mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 15:25:50 +00:00
fix(export): restore dialog scroll and adaptive format grid
This commit is contained in:
@@ -655,6 +655,11 @@
|
|||||||
&.checked {
|
&.checked {
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.session-cell {
|
.session-cell {
|
||||||
@@ -736,6 +741,12 @@
|
|||||||
&.running {
|
&.running {
|
||||||
background: color-mix(in srgb, var(--primary) 80%, #000);
|
background: color-mix(in srgb, var(--primary) 80%, #000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.no-session {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
border: 1px dashed var(--border-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-export-time {
|
.row-export-time {
|
||||||
@@ -816,17 +827,29 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.export-dialog {
|
.export-dialog {
|
||||||
width: min(980px, calc(100vw - 40px));
|
width: min(1080px, calc(100vw - 32px));
|
||||||
max-height: calc(100vh - 60px);
|
max-height: calc(100vh - 32px);
|
||||||
overflow: auto;
|
|
||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
padding: 16px;
|
padding: 14px 14px 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-body {
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
@@ -859,7 +882,6 @@
|
|||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
margin-bottom: 10px;
|
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
@@ -912,17 +934,23 @@
|
|||||||
|
|
||||||
.format-grid {
|
.format-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(130px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.format-card {
|
.format-card {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 82px;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
.format-label {
|
.format-label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -1033,9 +1061,13 @@
|
|||||||
|
|
||||||
.dialog-actions {
|
.dialog-actions {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: var(--card-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.primary-btn,
|
.primary-btn,
|
||||||
@@ -1132,7 +1164,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.format-grid {
|
.format-grid {
|
||||||
grid-template-columns: repeat(2, minmax(120px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.display-name-options {
|
.display-name-options {
|
||||||
@@ -1143,3 +1175,23 @@
|
|||||||
grid-template-columns: repeat(2, minmax(120px, 1fr));
|
grid-template-columns: repeat(2, minmax(120px, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.export-dialog-overlay {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dialog {
|
||||||
|
width: calc(100vw - 20px);
|
||||||
|
max-height: calc(100vh - 20px);
|
||||||
|
padding: 12px 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.format-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ interface ExportOptions {
|
|||||||
interface SessionRow extends AppChatSession {
|
interface SessionRow extends AppChatSession {
|
||||||
kind: ConversationTab
|
kind: ConversationTab
|
||||||
wechatId?: string
|
wechatId?: string
|
||||||
|
hasSession: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TaskProgress {
|
interface TaskProgress {
|
||||||
@@ -207,6 +208,13 @@ const toKindByContactType = (session: AppChatSession, contact?: ContactInfo): Co
|
|||||||
return 'private'
|
return 'private'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toKindByContact = (contact: ContactInfo): ConversationTab => {
|
||||||
|
if (contact.type === 'group') return 'group'
|
||||||
|
if (contact.type === 'official') return 'official'
|
||||||
|
if (contact.type === 'former_friend') return 'former_friend'
|
||||||
|
return 'private'
|
||||||
|
}
|
||||||
|
|
||||||
const isContentScopeSession = (session: SessionRow): boolean => (
|
const isContentScopeSession = (session: SessionRow): boolean => (
|
||||||
session.kind === 'private' || session.kind === 'group' || session.kind === 'former_friend'
|
session.kind === 'private' || session.kind === 'group' || session.kind === 'former_friend'
|
||||||
)
|
)
|
||||||
@@ -257,6 +265,50 @@ const toSessionRowsWithContacts = (
|
|||||||
sessions: AppChatSession[],
|
sessions: AppChatSession[],
|
||||||
contactMap: Record<string, ContactInfo>
|
contactMap: Record<string, ContactInfo>
|
||||||
): SessionRow[] => {
|
): SessionRow[] => {
|
||||||
|
const sessionMap = new Map<string, AppChatSession>()
|
||||||
|
for (const session of sessions || []) {
|
||||||
|
sessionMap.set(session.username, session)
|
||||||
|
}
|
||||||
|
|
||||||
|
const contacts = Object.values(contactMap)
|
||||||
|
.filter((contact) => (
|
||||||
|
contact.type === 'friend' ||
|
||||||
|
contact.type === 'group' ||
|
||||||
|
contact.type === 'official' ||
|
||||||
|
contact.type === 'former_friend'
|
||||||
|
))
|
||||||
|
|
||||||
|
if (contacts.length > 0) {
|
||||||
|
return contacts
|
||||||
|
.map((contact) => {
|
||||||
|
const session = sessionMap.get(contact.username)
|
||||||
|
const latestTs = session?.sortTimestamp || session?.lastTimestamp || 0
|
||||||
|
return {
|
||||||
|
...(session || {
|
||||||
|
username: contact.username,
|
||||||
|
type: 0,
|
||||||
|
unreadCount: 0,
|
||||||
|
summary: '',
|
||||||
|
sortTimestamp: latestTs,
|
||||||
|
lastTimestamp: latestTs,
|
||||||
|
lastMsgType: 0
|
||||||
|
}),
|
||||||
|
username: contact.username,
|
||||||
|
kind: toKindByContact(contact),
|
||||||
|
wechatId: contact.username,
|
||||||
|
displayName: contact.displayName || session?.displayName || contact.username,
|
||||||
|
avatarUrl: contact.avatarUrl || session?.avatarUrl,
|
||||||
|
hasSession: Boolean(session)
|
||||||
|
} as SessionRow
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
const latestA = a.sortTimestamp || a.lastTimestamp || 0
|
||||||
|
const latestB = b.sortTimestamp || b.lastTimestamp || 0
|
||||||
|
if (latestA !== latestB) return latestB - latestA
|
||||||
|
return (a.displayName || a.username).localeCompare(b.displayName || b.username, 'zh-Hans-CN')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return sessions
|
return sessions
|
||||||
.map((session) => {
|
.map((session) => {
|
||||||
const contact = contactMap[session.username]
|
const contact = contactMap[session.username]
|
||||||
@@ -265,7 +317,8 @@ const toSessionRowsWithContacts = (
|
|||||||
kind: toKindByContactType(session, contact),
|
kind: toKindByContactType(session, contact),
|
||||||
wechatId: contact?.username || session.username,
|
wechatId: contact?.username || session.username,
|
||||||
displayName: contact?.displayName || session.displayName || session.username,
|
displayName: contact?.displayName || session.displayName || session.username,
|
||||||
avatarUrl: contact?.avatarUrl || session.avatarUrl
|
avatarUrl: contact?.avatarUrl || session.avatarUrl,
|
||||||
|
hasSession: true
|
||||||
} as SessionRow
|
} as SessionRow
|
||||||
})
|
})
|
||||||
.sort((a, b) => (b.sortTimestamp || b.lastTimestamp || 0) - (a.sortTimestamp || a.lastTimestamp || 0))
|
.sort((a, b) => (b.sortTimestamp || b.lastTimestamp || 0) - (a.sortTimestamp || a.lastTimestamp || 0))
|
||||||
@@ -570,6 +623,9 @@ function ExportPage() {
|
|||||||
const cachedContactMap = toContactMapFromCaches(cachedContacts, cachedAvatarEntries)
|
const cachedContactMap = toContactMapFromCaches(cachedContacts, cachedAvatarEntries)
|
||||||
if (cachedContacts.length > 0) {
|
if (cachedContacts.length > 0) {
|
||||||
syncContactTypeCounts(Object.values(cachedContactMap))
|
syncContactTypeCounts(Object.values(cachedContactMap))
|
||||||
|
setSessions(toSessionRowsWithContacts([], cachedContactMap))
|
||||||
|
setSessionDataSource('cache')
|
||||||
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
setSessionContactsUpdatedAt(cachedContactsItem?.updatedAt || null)
|
setSessionContactsUpdatedAt(cachedContactsItem?.updatedAt || null)
|
||||||
setSessionAvatarUpdatedAt(cachedAvatarItem?.updatedAt || null)
|
setSessionAvatarUpdatedAt(cachedAvatarItem?.updatedAt || null)
|
||||||
@@ -800,6 +856,8 @@ function ExportPage() {
|
|||||||
const selectedCount = selectedSessions.size
|
const selectedCount = selectedSessions.size
|
||||||
|
|
||||||
const toggleSelectSession = (sessionId: string) => {
|
const toggleSelectSession = (sessionId: string) => {
|
||||||
|
const target = sessions.find(session => session.username === sessionId)
|
||||||
|
if (!target?.hasSession) return
|
||||||
setSelectedSessions(prev => {
|
setSelectedSessions(prev => {
|
||||||
const next = new Set(prev)
|
const next = new Set(prev)
|
||||||
if (next.has(sessionId)) {
|
if (next.has(sessionId)) {
|
||||||
@@ -812,7 +870,7 @@ function ExportPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const toggleSelectAllVisible = () => {
|
const toggleSelectAllVisible = () => {
|
||||||
const visibleIds = visibleSessions.map(session => session.username)
|
const visibleIds = visibleSessions.filter(session => session.hasSession).map(session => session.username)
|
||||||
if (visibleIds.length === 0) return
|
if (visibleIds.length === 0) return
|
||||||
|
|
||||||
setSelectedSessions(prev => {
|
setSelectedSessions(prev => {
|
||||||
@@ -1171,6 +1229,7 @@ function ExportPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openSingleExport = (session: SessionRow) => {
|
const openSingleExport = (session: SessionRow) => {
|
||||||
|
if (!session.hasSession) return
|
||||||
openExportDialog({
|
openExportDialog({
|
||||||
scope: 'single',
|
scope: 'single',
|
||||||
sessionIds: [session.username],
|
sessionIds: [session.username],
|
||||||
@@ -1180,7 +1239,8 @@ function ExportPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openBatchExport = () => {
|
const openBatchExport = () => {
|
||||||
const ids = Array.from(selectedSessions)
|
const selectable = new Set(sessions.filter(session => session.hasSession).map(session => session.username))
|
||||||
|
const ids = Array.from(selectedSessions).filter(id => selectable.has(id))
|
||||||
if (ids.length === 0) return
|
if (ids.length === 0) return
|
||||||
const nameMap = new Map(sessions.map(session => [session.username, session.displayName || session.username]))
|
const nameMap = new Map(sessions.map(session => [session.username, session.displayName || session.username]))
|
||||||
const names = ids.map(id => nameMap.get(id) || id)
|
const names = ids.map(id => nameMap.get(id) || id)
|
||||||
@@ -1195,11 +1255,11 @@ function ExportPage() {
|
|||||||
|
|
||||||
const openContentExport = (contentType: ContentType) => {
|
const openContentExport = (contentType: ContentType) => {
|
||||||
const ids = sessions
|
const ids = sessions
|
||||||
.filter(isContentScopeSession)
|
.filter(session => session.hasSession && isContentScopeSession(session))
|
||||||
.map(session => session.username)
|
.map(session => session.username)
|
||||||
|
|
||||||
const names = sessions
|
const names = sessions
|
||||||
.filter(isContentScopeSession)
|
.filter(session => session.hasSession && isContentScopeSession(session))
|
||||||
.map(session => session.displayName || session.username)
|
.map(session => session.displayName || session.username)
|
||||||
|
|
||||||
openExportDialog({
|
openExportDialog({
|
||||||
@@ -1320,6 +1380,16 @@ function ExportPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderActionCell = (session: SessionRow) => {
|
const renderActionCell = (session: SessionRow) => {
|
||||||
|
if (!session.hasSession) {
|
||||||
|
return (
|
||||||
|
<div className="row-action-cell">
|
||||||
|
<button className="row-export-btn no-session" disabled>
|
||||||
|
暂无会话
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const isRunning = runningSessionIds.has(session.username)
|
const isRunning = runningSessionIds.has(session.username)
|
||||||
const isQueued = queuedSessionIds.has(session.username)
|
const isQueued = queuedSessionIds.has(session.username)
|
||||||
const recent = formatRecentExportTime(lastExportBySession[session.username], nowTick)
|
const recent = formatRecentExportTime(lastExportBySession[session.username], nowTick)
|
||||||
@@ -1347,22 +1417,24 @@ function ExportPage() {
|
|||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<th className="sticky-col">选择</th>
|
<th className="sticky-col">选择</th>
|
||||||
<th>会话名(头像/名称/微信号)</th>
|
<th>联系人(头像/名称/微信号)</th>
|
||||||
<th className="sticky-right">操作</th>
|
<th className="sticky-right">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderRowCells = (session: SessionRow) => {
|
const renderRowCells = (session: SessionRow) => {
|
||||||
const checked = selectedSessions.has(session.username)
|
const selectable = session.hasSession
|
||||||
|
const checked = selectable && selectedSessions.has(session.username)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<td className="sticky-col">
|
<td className="sticky-col">
|
||||||
<button
|
<button
|
||||||
className={`select-icon-btn ${checked ? 'checked' : ''}`}
|
className={`select-icon-btn ${checked ? 'checked' : ''}`}
|
||||||
|
disabled={!selectable}
|
||||||
onClick={() => toggleSelectSession(session.username)}
|
onClick={() => toggleSelectSession(session.username)}
|
||||||
title={checked ? '取消选择' : '选择会话'}
|
title={selectable ? (checked ? '取消选择' : '选择会话') : '该联系人暂无会话记录'}
|
||||||
>
|
>
|
||||||
{checked ? <CheckSquare size={16} /> : <Square size={16} />}
|
{checked ? <CheckSquare size={16} /> : <Square size={16} />}
|
||||||
</button>
|
</button>
|
||||||
@@ -1661,134 +1733,136 @@ function ExportPage() {
|
|||||||
|
|
||||||
{exportDialog.open && (
|
{exportDialog.open && (
|
||||||
<div className="export-dialog-overlay" onClick={closeExportDialog}>
|
<div className="export-dialog-overlay" onClick={closeExportDialog}>
|
||||||
<div className="export-dialog" onClick={(event) => event.stopPropagation()}>
|
<div className="export-dialog" role="dialog" aria-modal="true" onClick={(event) => event.stopPropagation()}>
|
||||||
<div className="dialog-header">
|
<div className="dialog-header">
|
||||||
<h3>{exportDialog.title}</h3>
|
<h3>{exportDialog.title}</h3>
|
||||||
<button className="close-icon-btn" onClick={closeExportDialog}><X size={16} /></button>
|
<button className="close-icon-btn" onClick={closeExportDialog}><X size={16} /></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="dialog-section">
|
<div className="dialog-body">
|
||||||
<h4>导出范围</h4>
|
<div className="dialog-section">
|
||||||
<div className="scope-tag-row">
|
<h4>导出范围</h4>
|
||||||
<span className="scope-tag">{scopeLabel}</span>
|
<div className="scope-tag-row">
|
||||||
<span className="scope-count">{scopeCountLabel}</span>
|
<span className="scope-tag">{scopeLabel}</span>
|
||||||
</div>
|
<span className="scope-count">{scopeCountLabel}</span>
|
||||||
<div className="scope-list">
|
</div>
|
||||||
{exportDialog.sessionNames.slice(0, 20).map(name => (
|
<div className="scope-list">
|
||||||
<span key={name} className="scope-item">{name}</span>
|
{exportDialog.sessionNames.slice(0, 20).map(name => (
|
||||||
))}
|
<span key={name} className="scope-item">{name}</span>
|
||||||
{exportDialog.sessionNames.length > 20 && <span className="scope-item">... 还有 {exportDialog.sessionNames.length - 20} 个</span>}
|
))}
|
||||||
</div>
|
{exportDialog.sessionNames.length > 20 && <span className="scope-item">... 还有 {exportDialog.sessionNames.length - 20} 个</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="dialog-section">
|
|
||||||
<h4>对话文本导出格式选择</h4>
|
|
||||||
<div className="format-grid">
|
|
||||||
{formatCandidateOptions.map(option => (
|
|
||||||
<button
|
|
||||||
key={option.value}
|
|
||||||
className={`format-card ${options.format === option.value ? 'active' : ''}`}
|
|
||||||
onClick={() => setOptions(prev => ({ ...prev, format: option.value }))}
|
|
||||||
>
|
|
||||||
<div className="format-label">{option.label}</div>
|
|
||||||
<div className="format-desc">{option.desc}</div>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="dialog-section">
|
|
||||||
<h4>时间范围</h4>
|
|
||||||
<div className="switch-row">
|
|
||||||
<span>导出全部时间</span>
|
|
||||||
<label className="switch">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={options.useAllTime}
|
|
||||||
onChange={(event) => setOptions(prev => ({ ...prev, useAllTime: event.target.checked }))}
|
|
||||||
/>
|
|
||||||
<span className="switch-slider"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!options.useAllTime && options.dateRange && (
|
<div className="dialog-section">
|
||||||
<div className="date-range-row">
|
<h4>对话文本导出格式选择</h4>
|
||||||
<label>
|
<div className="format-grid">
|
||||||
开始
|
{formatCandidateOptions.map(option => (
|
||||||
|
<button
|
||||||
|
key={option.value}
|
||||||
|
className={`format-card ${options.format === option.value ? 'active' : ''}`}
|
||||||
|
onClick={() => setOptions(prev => ({ ...prev, format: option.value }))}
|
||||||
|
>
|
||||||
|
<div className="format-label">{option.label}</div>
|
||||||
|
<div className="format-desc">{option.desc}</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="dialog-section">
|
||||||
|
<h4>时间范围</h4>
|
||||||
|
<div className="switch-row">
|
||||||
|
<span>导出全部时间</span>
|
||||||
|
<label className="switch">
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="checkbox"
|
||||||
value={formatDateInputValue(options.dateRange.start)}
|
checked={options.useAllTime}
|
||||||
onChange={(event) => {
|
onChange={(event) => setOptions(prev => ({ ...prev, useAllTime: event.target.checked }))}
|
||||||
const start = parseDateInput(event.target.value, false)
|
|
||||||
setOptions(prev => ({
|
|
||||||
...prev,
|
|
||||||
dateRange: prev.dateRange ? {
|
|
||||||
start,
|
|
||||||
end: prev.dateRange.end < start ? parseDateInput(event.target.value, true) : prev.dateRange.end
|
|
||||||
} : { start, end: new Date() }
|
|
||||||
}))
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
结束
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
value={formatDateInputValue(options.dateRange.end)}
|
|
||||||
onChange={(event) => {
|
|
||||||
const end = parseDateInput(event.target.value, true)
|
|
||||||
setOptions(prev => ({
|
|
||||||
...prev,
|
|
||||||
dateRange: prev.dateRange ? {
|
|
||||||
start: prev.dateRange.start > end ? parseDateInput(event.target.value, false) : prev.dateRange.start,
|
|
||||||
end
|
|
||||||
} : { start: new Date(), end }
|
|
||||||
}))
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
<span className="switch-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="dialog-section">
|
{!options.useAllTime && options.dateRange && (
|
||||||
<h4>媒体与头像</h4>
|
<div className="date-range-row">
|
||||||
<div className="switch-row">
|
<label>
|
||||||
<span>导出媒体文件</span>
|
开始
|
||||||
<label className="switch">
|
<input
|
||||||
<input
|
type="date"
|
||||||
type="checkbox"
|
value={formatDateInputValue(options.dateRange.start)}
|
||||||
checked={options.exportMedia}
|
onChange={(event) => {
|
||||||
onChange={(event) => setOptions(prev => ({ ...prev, exportMedia: event.target.checked }))}
|
const start = parseDateInput(event.target.value, false)
|
||||||
/>
|
setOptions(prev => ({
|
||||||
<span className="switch-slider"></span>
|
...prev,
|
||||||
</label>
|
dateRange: prev.dateRange ? {
|
||||||
|
start,
|
||||||
|
end: prev.dateRange.end < start ? parseDateInput(event.target.value, true) : prev.dateRange.end
|
||||||
|
} : { start, end: new Date() }
|
||||||
|
}))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
结束
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={formatDateInputValue(options.dateRange.end)}
|
||||||
|
onChange={(event) => {
|
||||||
|
const end = parseDateInput(event.target.value, true)
|
||||||
|
setOptions(prev => ({
|
||||||
|
...prev,
|
||||||
|
dateRange: prev.dateRange ? {
|
||||||
|
start: prev.dateRange.start > end ? parseDateInput(event.target.value, false) : prev.dateRange.start,
|
||||||
|
end
|
||||||
|
} : { start: new Date(), end }
|
||||||
|
}))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="media-check-grid">
|
<div className="dialog-section">
|
||||||
<label><input type="checkbox" checked={options.exportImages} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportImages: event.target.checked }))} /> 图片</label>
|
<h4>媒体与头像</h4>
|
||||||
<label><input type="checkbox" checked={options.exportVoices} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportVoices: event.target.checked }))} /> 语音</label>
|
<div className="switch-row">
|
||||||
<label><input type="checkbox" checked={options.exportVideos} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportVideos: event.target.checked }))} /> 视频</label>
|
<span>导出媒体文件</span>
|
||||||
<label><input type="checkbox" checked={options.exportEmojis} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportEmojis: event.target.checked }))} /> 表情包</label>
|
<label className="switch">
|
||||||
<label><input type="checkbox" checked={options.exportVoiceAsText} onChange={event => setOptions(prev => ({ ...prev, exportVoiceAsText: event.target.checked }))} /> 语音转文字</label>
|
|
||||||
<label><input type="checkbox" checked={options.exportAvatars} onChange={event => setOptions(prev => ({ ...prev, exportAvatars: event.target.checked }))} /> 导出头像</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="dialog-section">
|
|
||||||
<h4>发送者名称显示</h4>
|
|
||||||
<div className="display-name-options">
|
|
||||||
{displayNameOptions.map(option => (
|
|
||||||
<label key={option.value} className={`display-name-item ${options.displayNamePreference === option.value ? 'active' : ''}`}>
|
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="checkbox"
|
||||||
checked={options.displayNamePreference === option.value}
|
checked={options.exportMedia}
|
||||||
onChange={() => setOptions(prev => ({ ...prev, displayNamePreference: option.value }))}
|
onChange={(event) => setOptions(prev => ({ ...prev, exportMedia: event.target.checked }))}
|
||||||
/>
|
/>
|
||||||
<span>{option.label}</span>
|
<span className="switch-slider"></span>
|
||||||
<small>{option.desc}</small>
|
|
||||||
</label>
|
</label>
|
||||||
))}
|
</div>
|
||||||
|
|
||||||
|
<div className="media-check-grid">
|
||||||
|
<label><input type="checkbox" checked={options.exportImages} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportImages: event.target.checked }))} /> 图片</label>
|
||||||
|
<label><input type="checkbox" checked={options.exportVoices} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportVoices: event.target.checked }))} /> 语音</label>
|
||||||
|
<label><input type="checkbox" checked={options.exportVideos} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportVideos: event.target.checked }))} /> 视频</label>
|
||||||
|
<label><input type="checkbox" checked={options.exportEmojis} disabled={!options.exportMedia} onChange={event => setOptions(prev => ({ ...prev, exportEmojis: event.target.checked }))} /> 表情包</label>
|
||||||
|
<label><input type="checkbox" checked={options.exportVoiceAsText} onChange={event => setOptions(prev => ({ ...prev, exportVoiceAsText: event.target.checked }))} /> 语音转文字</label>
|
||||||
|
<label><input type="checkbox" checked={options.exportAvatars} onChange={event => setOptions(prev => ({ ...prev, exportAvatars: event.target.checked }))} /> 导出头像</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="dialog-section">
|
||||||
|
<h4>发送者名称显示</h4>
|
||||||
|
<div className="display-name-options">
|
||||||
|
{displayNameOptions.map(option => (
|
||||||
|
<label key={option.value} className={`display-name-item ${options.displayNamePreference === option.value ? 'active' : ''}`}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
checked={options.displayNamePreference === option.value}
|
||||||
|
onChange={() => setOptions(prev => ({ ...prev, displayNamePreference: option.value }))}
|
||||||
|
/>
|
||||||
|
<span>{option.label}</span>
|
||||||
|
<small>{option.desc}</small>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user