mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
refactor(sns): move jump calendar to header
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect, useRef, useState, useCallback } from 'react'
|
import React from 'react'
|
||||||
import { Search, Calendar, User, X, Loader2 } from 'lucide-react'
|
import { Search, User, X, Loader2 } from 'lucide-react'
|
||||||
import { Avatar } from '../Avatar'
|
import { Avatar } from '../Avatar'
|
||||||
import JumpToDatePopover from '../JumpToDatePopover'
|
|
||||||
|
|
||||||
interface Contact {
|
interface Contact {
|
||||||
username: string
|
username: string
|
||||||
@@ -20,8 +19,6 @@ interface ContactsCountProgress {
|
|||||||
interface SnsFilterPanelProps {
|
interface SnsFilterPanelProps {
|
||||||
searchKeyword: string
|
searchKeyword: string
|
||||||
setSearchKeyword: (val: string) => void
|
setSearchKeyword: (val: string) => void
|
||||||
jumpTargetDate?: Date
|
|
||||||
setJumpTargetDate: (date?: Date) => void
|
|
||||||
totalFriendsLabel?: string
|
totalFriendsLabel?: string
|
||||||
selectedUsernames: string[]
|
selectedUsernames: string[]
|
||||||
setSelectedUsernames: (val: string[]) => void
|
setSelectedUsernames: (val: string[]) => void
|
||||||
@@ -35,8 +32,6 @@ interface SnsFilterPanelProps {
|
|||||||
export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
||||||
searchKeyword,
|
searchKeyword,
|
||||||
setSearchKeyword,
|
setSearchKeyword,
|
||||||
jumpTargetDate,
|
|
||||||
setJumpTargetDate,
|
|
||||||
totalFriendsLabel,
|
totalFriendsLabel,
|
||||||
selectedUsernames,
|
selectedUsernames,
|
||||||
setSelectedUsernames,
|
setSelectedUsernames,
|
||||||
@@ -46,104 +41,6 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
loading,
|
loading,
|
||||||
contactsCountProgress
|
contactsCountProgress
|
||||||
}) => {
|
}) => {
|
||||||
const [showJumpPopover, setShowJumpPopover] = useState(false)
|
|
||||||
const [jumpPopoverDate, setJumpPopoverDate] = useState<Date>(jumpTargetDate || new Date())
|
|
||||||
const [jumpDateCounts, setJumpDateCounts] = useState<Record<string, number>>({})
|
|
||||||
const [jumpDateMessageDates, setJumpDateMessageDates] = useState<Set<string>>(new Set())
|
|
||||||
const [hasLoadedJumpDateCounts, setHasLoadedJumpDateCounts] = useState(false)
|
|
||||||
const [loadingJumpDateCounts, setLoadingJumpDateCounts] = useState(false)
|
|
||||||
const jumpCalendarWrapRef = useRef<HTMLDivElement | null>(null)
|
|
||||||
const jumpDateCountsCacheRef = useRef<Map<string, Record<string, number>>>(new Map())
|
|
||||||
const jumpDateRequestSeqRef = useRef(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!showJumpPopover) return
|
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
|
||||||
if (!jumpCalendarWrapRef.current) return
|
|
||||||
if (jumpCalendarWrapRef.current.contains(event.target as Node)) return
|
|
||||||
setShowJumpPopover(false)
|
|
||||||
}
|
|
||||||
document.addEventListener('mousedown', handleClickOutside)
|
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
||||||
}, [showJumpPopover])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (showJumpPopover) return
|
|
||||||
setJumpPopoverDate(jumpTargetDate || new Date())
|
|
||||||
}, [jumpTargetDate, showJumpPopover])
|
|
||||||
|
|
||||||
const toMonthKey = useCallback((date: Date) => {
|
|
||||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const toDateKey = useCallback((timestampSeconds: number) => {
|
|
||||||
const date = new Date(timestampSeconds * 1000)
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
||||||
const day = String(date.getDate()).padStart(2, '0')
|
|
||||||
return `${year}-${month}-${day}`
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const applyJumpDateCounts = useCallback((counts: Record<string, number>) => {
|
|
||||||
setJumpDateCounts(counts)
|
|
||||||
setJumpDateMessageDates(new Set(Object.keys(counts)))
|
|
||||||
setHasLoadedJumpDateCounts(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const loadJumpDateCounts = useCallback(async (monthDate: Date) => {
|
|
||||||
const monthKey = toMonthKey(monthDate)
|
|
||||||
const cached = jumpDateCountsCacheRef.current.get(monthKey)
|
|
||||||
if (cached) {
|
|
||||||
applyJumpDateCounts(cached)
|
|
||||||
setLoadingJumpDateCounts(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestSeq = ++jumpDateRequestSeqRef.current
|
|
||||||
setLoadingJumpDateCounts(true)
|
|
||||||
setHasLoadedJumpDateCounts(false)
|
|
||||||
|
|
||||||
const year = monthDate.getFullYear()
|
|
||||||
const month = monthDate.getMonth()
|
|
||||||
const monthStart = new Date(year, month, 1, 0, 0, 0, 0)
|
|
||||||
const monthEnd = new Date(year, month + 1, 0, 23, 59, 59, 999)
|
|
||||||
const startTime = Math.floor(monthStart.getTime() / 1000)
|
|
||||||
const endTime = Math.floor(monthEnd.getTime() / 1000)
|
|
||||||
const pageSize = 200
|
|
||||||
let offset = 0
|
|
||||||
const counts: Record<string, number> = {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
const result = await window.electronAPI.sns.getTimeline(pageSize, offset, [], '', startTime, endTime)
|
|
||||||
if (!result?.success || !Array.isArray(result.timeline) || result.timeline.length === 0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
result.timeline.forEach((post) => {
|
|
||||||
const key = toDateKey(Number(post.createTime || 0))
|
|
||||||
if (!key) return
|
|
||||||
counts[key] = (counts[key] || 0) + 1
|
|
||||||
})
|
|
||||||
if (result.timeline.length < pageSize) break
|
|
||||||
offset += pageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestSeq !== jumpDateRequestSeqRef.current) return
|
|
||||||
jumpDateCountsCacheRef.current.set(monthKey, counts)
|
|
||||||
applyJumpDateCounts(counts)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载朋友圈按日条数失败:', error)
|
|
||||||
if (requestSeq !== jumpDateRequestSeqRef.current) return
|
|
||||||
setJumpDateCounts({})
|
|
||||||
setJumpDateMessageDates(new Set())
|
|
||||||
setHasLoadedJumpDateCounts(true)
|
|
||||||
} finally {
|
|
||||||
if (requestSeq === jumpDateRequestSeqRef.current) {
|
|
||||||
setLoadingJumpDateCounts(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [applyJumpDateCounts, toDateKey, toMonthKey])
|
|
||||||
|
|
||||||
const filteredContacts = contacts.filter(c =>
|
const filteredContacts = contacts.filter(c =>
|
||||||
(c.displayName || '').toLowerCase().includes(contactSearch.toLowerCase()) ||
|
(c.displayName || '').toLowerCase().includes(contactSearch.toLowerCase()) ||
|
||||||
c.username.toLowerCase().includes(contactSearch.toLowerCase())
|
c.username.toLowerCase().includes(contactSearch.toLowerCase())
|
||||||
@@ -153,7 +50,6 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
if (selectedUsernames.includes(username)) {
|
if (selectedUsernames.includes(username)) {
|
||||||
setSelectedUsernames(selectedUsernames.filter(u => u !== username))
|
setSelectedUsernames(selectedUsernames.filter(u => u !== username))
|
||||||
} else {
|
} else {
|
||||||
setJumpTargetDate(undefined) // Reset date jump when selecting user
|
|
||||||
setSelectedUsernames([...selectedUsernames, username])
|
setSelectedUsernames([...selectedUsernames, username])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +57,6 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
const clearFilters = () => {
|
const clearFilters = () => {
|
||||||
setSearchKeyword('')
|
setSearchKeyword('')
|
||||||
setSelectedUsernames([])
|
setSelectedUsernames([])
|
||||||
setJumpTargetDate(undefined)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEmptyStateText = () => {
|
const getEmptyStateText = () => {
|
||||||
@@ -178,7 +73,7 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
<aside className="sns-filter-panel">
|
<aside className="sns-filter-panel">
|
||||||
<div className="filter-header">
|
<div className="filter-header">
|
||||||
<h3>筛选条件</h3>
|
<h3>筛选条件</h3>
|
||||||
{(searchKeyword || jumpTargetDate || selectedUsernames.length > 0) && (
|
{(searchKeyword || selectedUsernames.length > 0) && (
|
||||||
<button className="reset-all-btn" onClick={clearFilters} title="重置所有筛选">
|
<button className="reset-all-btn" onClick={clearFilters} title="重置所有筛选">
|
||||||
<RefreshCw size={14} />
|
<RefreshCw size={14} />
|
||||||
</button>
|
</button>
|
||||||
@@ -206,64 +101,6 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Date Widget */}
|
|
||||||
<div className="filter-widget date-widget">
|
|
||||||
<div className="date-widget-row">
|
|
||||||
<div className="widget-header">
|
|
||||||
<Calendar size={14} />
|
|
||||||
<span>时间跳转</span>
|
|
||||||
</div>
|
|
||||||
<div className="jump-calendar-anchor" ref={jumpCalendarWrapRef}>
|
|
||||||
<button
|
|
||||||
className={`date-picker-trigger ${jumpTargetDate ? 'active' : ''}`}
|
|
||||||
onClick={() => {
|
|
||||||
if (!showJumpPopover) {
|
|
||||||
const nextDate = jumpTargetDate || new Date()
|
|
||||||
setJumpPopoverDate(nextDate)
|
|
||||||
void loadJumpDateCounts(nextDate)
|
|
||||||
}
|
|
||||||
setShowJumpPopover(prev => !prev)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="date-text">
|
|
||||||
{jumpTargetDate
|
|
||||||
? jumpTargetDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })
|
|
||||||
: '选择日期...'}
|
|
||||||
</span>
|
|
||||||
{jumpTargetDate && (
|
|
||||||
<div
|
|
||||||
className="clear-date-btn"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
setJumpTargetDate(undefined)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<X size={12} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<JumpToDatePopover
|
|
||||||
isOpen={showJumpPopover}
|
|
||||||
currentDate={jumpPopoverDate}
|
|
||||||
onClose={() => setShowJumpPopover(false)}
|
|
||||||
onMonthChange={(date) => {
|
|
||||||
setJumpPopoverDate(date)
|
|
||||||
void loadJumpDateCounts(date)
|
|
||||||
}}
|
|
||||||
onSelect={(date) => {
|
|
||||||
setJumpPopoverDate(date)
|
|
||||||
setJumpTargetDate(date)
|
|
||||||
}}
|
|
||||||
messageDates={jumpDateMessageDates}
|
|
||||||
hasLoadedMessageDates={hasLoadedJumpDateCounts}
|
|
||||||
messageDateCounts={jumpDateCounts}
|
|
||||||
loadingDateCounts={loadingJumpDateCounts}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Contact Widget */}
|
{/* Contact Widget */}
|
||||||
<div className="filter-widget contact-widget">
|
<div className="filter-widget contact-widget">
|
||||||
<div className="widget-header">
|
<div className="widget-header">
|
||||||
|
|||||||
@@ -1074,26 +1074,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Date Widget */
|
|
||||||
.date-widget-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-widget .widget-header {
|
|
||||||
margin-bottom: 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
min-width: 72px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-calendar-anchor {
|
.jump-calendar-anchor {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
|
|
||||||
@@ -1102,43 +1086,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-picker-trigger {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: var(--sns-border-radius-sm);
|
|
||||||
padding: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border-color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: rgba(var(--primary-rgb), 0.08);
|
|
||||||
border-color: var(--primary);
|
|
||||||
color: var(--primary);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear-date-btn {
|
|
||||||
padding: 4px;
|
|
||||||
display: flex;
|
|
||||||
color: var(--primary);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Contact Widget - Refactored */
|
/* Contact Widget - Refactored */
|
||||||
.contact-widget {
|
.contact-widget {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { SnsPostItem } from '../components/Sns/SnsPostItem'
|
|||||||
import { SnsFilterPanel } from '../components/Sns/SnsFilterPanel'
|
import { SnsFilterPanel } from '../components/Sns/SnsFilterPanel'
|
||||||
import { ContactSnsTimelineDialog } from '../components/Sns/ContactSnsTimelineDialog'
|
import { ContactSnsTimelineDialog } from '../components/Sns/ContactSnsTimelineDialog'
|
||||||
import type { ContactSnsTimelineTarget } from '../components/Sns/contactSnsTimeline'
|
import type { ContactSnsTimelineTarget } from '../components/Sns/contactSnsTimeline'
|
||||||
|
import JumpToDatePopover from '../components/JumpToDatePopover'
|
||||||
import * as configService from '../services/config'
|
import * as configService from '../services/config'
|
||||||
|
|
||||||
const SNS_PAGE_CACHE_TTL_MS = 24 * 60 * 60 * 1000
|
const SNS_PAGE_CACHE_TTL_MS = 24 * 60 * 60 * 1000
|
||||||
@@ -119,6 +120,12 @@ export default function SnsPage() {
|
|||||||
// UI states
|
// UI states
|
||||||
const [debugPost, setDebugPost] = useState<SnsPost | null>(null)
|
const [debugPost, setDebugPost] = useState<SnsPost | null>(null)
|
||||||
const [authorTimelineTarget, setAuthorTimelineTarget] = useState<ContactSnsTimelineTarget | null>(null)
|
const [authorTimelineTarget, setAuthorTimelineTarget] = useState<ContactSnsTimelineTarget | null>(null)
|
||||||
|
const [showJumpPopover, setShowJumpPopover] = useState(false)
|
||||||
|
const [jumpPopoverDate, setJumpPopoverDate] = useState<Date>(jumpTargetDate || new Date())
|
||||||
|
const [jumpDateCounts, setJumpDateCounts] = useState<Record<string, number>>({})
|
||||||
|
const [jumpDateMessageDates, setJumpDateMessageDates] = useState<Set<string>>(new Set())
|
||||||
|
const [hasLoadedJumpDateCounts, setHasLoadedJumpDateCounts] = useState(false)
|
||||||
|
const [loadingJumpDateCounts, setLoadingJumpDateCounts] = useState(false)
|
||||||
|
|
||||||
// 导出相关状态
|
// 导出相关状态
|
||||||
const [showExportDialog, setShowExportDialog] = useState(false)
|
const [showExportDialog, setShowExportDialog] = useState(false)
|
||||||
@@ -142,6 +149,7 @@ export default function SnsPage() {
|
|||||||
const [triggerMessage, setTriggerMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
|
const [triggerMessage, setTriggerMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
|
||||||
|
|
||||||
const postsContainerRef = useRef<HTMLDivElement>(null)
|
const postsContainerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const jumpCalendarWrapRef = useRef<HTMLDivElement | null>(null)
|
||||||
const [hasNewer, setHasNewer] = useState(false)
|
const [hasNewer, setHasNewer] = useState(false)
|
||||||
const [loadingNewer, setLoadingNewer] = useState(false)
|
const [loadingNewer, setLoadingNewer] = useState(false)
|
||||||
const postsRef = useRef<SnsPost[]>([])
|
const postsRef = useRef<SnsPost[]>([])
|
||||||
@@ -157,6 +165,8 @@ export default function SnsPage() {
|
|||||||
const contactsLoadTokenRef = useRef(0)
|
const contactsLoadTokenRef = useRef(0)
|
||||||
const contactsCountHydrationTokenRef = useRef(0)
|
const contactsCountHydrationTokenRef = useRef(0)
|
||||||
const contactsCountBatchTimerRef = useRef<number | null>(null)
|
const contactsCountBatchTimerRef = useRef<number | null>(null)
|
||||||
|
const jumpDateCountsCacheRef = useRef<Map<string, Record<string, number>>>(new Map())
|
||||||
|
const jumpDateRequestSeqRef = useRef(0)
|
||||||
|
|
||||||
// Sync posts ref
|
// Sync posts ref
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -180,6 +190,21 @@ export default function SnsPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
jumpTargetDateRef.current = jumpTargetDate
|
jumpTargetDateRef.current = jumpTargetDate
|
||||||
}, [jumpTargetDate])
|
}, [jumpTargetDate])
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showJumpPopover) {
|
||||||
|
setJumpPopoverDate(jumpTargetDate || new Date())
|
||||||
|
}
|
||||||
|
}, [jumpTargetDate, showJumpPopover])
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showJumpPopover) return
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (!jumpCalendarWrapRef.current) return
|
||||||
|
if (jumpCalendarWrapRef.current.contains(event.target as Node)) return
|
||||||
|
setShowJumpPopover(false)
|
||||||
|
}
|
||||||
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}, [showJumpPopover])
|
||||||
// 在 DOM 更新后、浏览器绘制前同步调整滚动位置,防止向上加载时页面跳动
|
// 在 DOM 更新后、浏览器绘制前同步调整滚动位置,防止向上加载时页面跳动
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const snapshot = scrollAdjustmentRef.current;
|
const snapshot = scrollAdjustmentRef.current;
|
||||||
@@ -221,6 +246,78 @@ export default function SnsPage() {
|
|||||||
return Math.max(0, Math.floor(numeric))
|
return Math.max(0, Math.floor(numeric))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const toMonthKey = useCallback((date: Date) => {
|
||||||
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const toDateKey = useCallback((timestampSeconds: number) => {
|
||||||
|
const date = new Date(timestampSeconds * 1000)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const applyJumpDateCounts = useCallback((counts: Record<string, number>) => {
|
||||||
|
setJumpDateCounts(counts)
|
||||||
|
setJumpDateMessageDates(new Set(Object.keys(counts)))
|
||||||
|
setHasLoadedJumpDateCounts(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const loadJumpDateCounts = useCallback(async (monthDate: Date) => {
|
||||||
|
const monthKey = toMonthKey(monthDate)
|
||||||
|
const cached = jumpDateCountsCacheRef.current.get(monthKey)
|
||||||
|
if (cached) {
|
||||||
|
applyJumpDateCounts(cached)
|
||||||
|
setLoadingJumpDateCounts(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestSeq = ++jumpDateRequestSeqRef.current
|
||||||
|
setLoadingJumpDateCounts(true)
|
||||||
|
setHasLoadedJumpDateCounts(false)
|
||||||
|
|
||||||
|
const year = monthDate.getFullYear()
|
||||||
|
const month = monthDate.getMonth()
|
||||||
|
const monthStart = new Date(year, month, 1, 0, 0, 0, 0)
|
||||||
|
const monthEnd = new Date(year, month + 1, 0, 23, 59, 59, 999)
|
||||||
|
const startTime = Math.floor(monthStart.getTime() / 1000)
|
||||||
|
const endTime = Math.floor(monthEnd.getTime() / 1000)
|
||||||
|
const pageSize = 200
|
||||||
|
let offset = 0
|
||||||
|
const counts: Record<string, number> = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const result = await window.electronAPI.sns.getTimeline(pageSize, offset, [], '', startTime, endTime)
|
||||||
|
if (!result?.success || !Array.isArray(result.timeline) || result.timeline.length === 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result.timeline.forEach((post) => {
|
||||||
|
const key = toDateKey(Number(post.createTime || 0))
|
||||||
|
if (!key) return
|
||||||
|
counts[key] = (counts[key] || 0) + 1
|
||||||
|
})
|
||||||
|
if (result.timeline.length < pageSize) break
|
||||||
|
offset += pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestSeq !== jumpDateRequestSeqRef.current) return
|
||||||
|
jumpDateCountsCacheRef.current.set(monthKey, counts)
|
||||||
|
applyJumpDateCounts(counts)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载朋友圈按日条数失败:', error)
|
||||||
|
if (requestSeq !== jumpDateRequestSeqRef.current) return
|
||||||
|
setJumpDateCounts({})
|
||||||
|
setJumpDateMessageDates(new Set())
|
||||||
|
setHasLoadedJumpDateCounts(true)
|
||||||
|
} finally {
|
||||||
|
if (requestSeq === jumpDateRequestSeqRef.current) {
|
||||||
|
setLoadingJumpDateCounts(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [applyJumpDateCounts, toDateKey, toMonthKey])
|
||||||
|
|
||||||
const compareContactsForRanking = useCallback((a: Contact, b: Contact): number => {
|
const compareContactsForRanking = useCallback((a: Contact, b: Contact): number => {
|
||||||
const aReady = a.postCountStatus === 'ready'
|
const aReady = a.postCountStatus === 'ready'
|
||||||
const bReady = b.postCountStatus === 'ready'
|
const bReady = b.postCountStatus === 'ready'
|
||||||
@@ -985,6 +1082,42 @@ export default function SnsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="header-actions">
|
<div className="header-actions">
|
||||||
|
<div className="jump-calendar-anchor" ref={jumpCalendarWrapRef}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`icon-btn ${showJumpPopover ? 'active' : ''}`}
|
||||||
|
title={jumpTargetDate
|
||||||
|
? jumpTargetDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })
|
||||||
|
: '时间跳转'}
|
||||||
|
onClick={() => {
|
||||||
|
if (!showJumpPopover) {
|
||||||
|
const nextDate = jumpTargetDate || new Date()
|
||||||
|
setJumpPopoverDate(nextDate)
|
||||||
|
void loadJumpDateCounts(nextDate)
|
||||||
|
}
|
||||||
|
setShowJumpPopover(prev => !prev)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Calendar size={20} />
|
||||||
|
</button>
|
||||||
|
<JumpToDatePopover
|
||||||
|
isOpen={showJumpPopover}
|
||||||
|
currentDate={jumpPopoverDate}
|
||||||
|
onClose={() => setShowJumpPopover(false)}
|
||||||
|
onMonthChange={(date) => {
|
||||||
|
setJumpPopoverDate(date)
|
||||||
|
void loadJumpDateCounts(date)
|
||||||
|
}}
|
||||||
|
onSelect={(date) => {
|
||||||
|
setJumpPopoverDate(date)
|
||||||
|
setJumpTargetDate(date)
|
||||||
|
}}
|
||||||
|
messageDates={jumpDateMessageDates}
|
||||||
|
hasLoadedMessageDates={hasLoadedJumpDateCounts}
|
||||||
|
messageDateCounts={jumpDateCounts}
|
||||||
|
loadingDateCounts={loadingJumpDateCounts}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setTriggerMessage(null)
|
setTriggerMessage(null)
|
||||||
@@ -1110,8 +1243,6 @@ export default function SnsPage() {
|
|||||||
<SnsFilterPanel
|
<SnsFilterPanel
|
||||||
searchKeyword={searchKeyword}
|
searchKeyword={searchKeyword}
|
||||||
setSearchKeyword={setSearchKeyword}
|
setSearchKeyword={setSearchKeyword}
|
||||||
jumpTargetDate={jumpTargetDate}
|
|
||||||
setJumpTargetDate={setJumpTargetDate}
|
|
||||||
totalFriendsLabel={
|
totalFriendsLabel={
|
||||||
overviewStatsStatus === 'loading'
|
overviewStatsStatus === 'loading'
|
||||||
? '统计中'
|
? '统计中'
|
||||||
|
|||||||
Reference in New Issue
Block a user