mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
feat(sns): show daily counts in jump calendar
This commit is contained in:
@@ -6,6 +6,7 @@ interface JumpToDatePopoverProps {
|
|||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onSelect: (date: Date) => void
|
onSelect: (date: Date) => void
|
||||||
|
onMonthChange?: (date: Date) => void
|
||||||
className?: string
|
className?: string
|
||||||
style?: React.CSSProperties
|
style?: React.CSSProperties
|
||||||
currentDate?: Date
|
currentDate?: Date
|
||||||
@@ -20,6 +21,7 @@ const JumpToDatePopover: React.FC<JumpToDatePopoverProps> = ({
|
|||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
onMonthChange,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
currentDate = new Date(),
|
currentDate = new Date(),
|
||||||
@@ -112,13 +114,17 @@ const JumpToDatePopover: React.FC<JumpToDatePopoverProps> = ({
|
|||||||
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
|
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
|
||||||
const days = generateCalendar()
|
const days = generateCalendar()
|
||||||
const mergedClassName = ['jump-date-popover', className || ''].join(' ').trim()
|
const mergedClassName = ['jump-date-popover', className || ''].join(' ').trim()
|
||||||
|
const updateCalendarDate = (nextDate: Date) => {
|
||||||
|
setCalendarDate(nextDate)
|
||||||
|
onMonthChange?.(nextDate)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={mergedClassName} style={style} role="dialog" aria-label="跳转日期">
|
<div className={mergedClassName} style={style} role="dialog" aria-label="跳转日期">
|
||||||
<div className="calendar-nav">
|
<div className="calendar-nav">
|
||||||
<button
|
<button
|
||||||
className="nav-btn"
|
className="nav-btn"
|
||||||
onClick={() => setCalendarDate(new Date(calendarDate.getFullYear(), calendarDate.getMonth() - 1, 1))}
|
onClick={() => updateCalendarDate(new Date(calendarDate.getFullYear(), calendarDate.getMonth() - 1, 1))}
|
||||||
aria-label="上一月"
|
aria-label="上一月"
|
||||||
>
|
>
|
||||||
<ChevronLeft size={16} />
|
<ChevronLeft size={16} />
|
||||||
@@ -126,7 +132,7 @@ const JumpToDatePopover: React.FC<JumpToDatePopoverProps> = ({
|
|||||||
<span className="current-month">{calendarDate.getFullYear()}年{calendarDate.getMonth() + 1}月</span>
|
<span className="current-month">{calendarDate.getFullYear()}年{calendarDate.getMonth() + 1}月</span>
|
||||||
<button
|
<button
|
||||||
className="nav-btn"
|
className="nav-btn"
|
||||||
onClick={() => setCalendarDate(new Date(calendarDate.getFullYear(), calendarDate.getMonth() + 1, 1))}
|
onClick={() => updateCalendarDate(new Date(calendarDate.getFullYear(), calendarDate.getMonth() + 1, 1))}
|
||||||
aria-label="下一月"
|
aria-label="下一月"
|
||||||
>
|
>
|
||||||
<ChevronRight size={16} />
|
<ChevronRight size={16} />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState, useCallback } from 'react'
|
||||||
import { Search, Calendar, User, X, Loader2 } from 'lucide-react'
|
import { Search, Calendar, User, X, Loader2 } from 'lucide-react'
|
||||||
import { Avatar } from '../Avatar'
|
import { Avatar } from '../Avatar'
|
||||||
import JumpToDatePopover from '../JumpToDatePopover'
|
import JumpToDatePopover from '../JumpToDatePopover'
|
||||||
@@ -46,7 +46,13 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [showJumpPopover, setShowJumpPopover] = useState(false)
|
const [showJumpPopover, setShowJumpPopover] = useState(false)
|
||||||
const [jumpPopoverDate, setJumpPopoverDate] = useState<Date>(jumpTargetDate || new Date())
|
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 jumpCalendarWrapRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
const jumpDateCountsCacheRef = useRef<Map<string, Record<string, number>>>(new Map())
|
||||||
|
const jumpDateRequestSeqRef = useRef(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showJumpPopover) return
|
if (!showJumpPopover) return
|
||||||
@@ -64,6 +70,78 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
setJumpPopoverDate(jumpTargetDate || new Date())
|
setJumpPopoverDate(jumpTargetDate || new Date())
|
||||||
}, [jumpTargetDate, showJumpPopover])
|
}, [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())
|
||||||
@@ -139,7 +217,9 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
className={`date-picker-trigger ${jumpTargetDate ? 'active' : ''}`}
|
className={`date-picker-trigger ${jumpTargetDate ? 'active' : ''}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!showJumpPopover) {
|
if (!showJumpPopover) {
|
||||||
setJumpPopoverDate(jumpTargetDate || new Date())
|
const nextDate = jumpTargetDate || new Date()
|
||||||
|
setJumpPopoverDate(nextDate)
|
||||||
|
void loadJumpDateCounts(nextDate)
|
||||||
}
|
}
|
||||||
setShowJumpPopover(prev => !prev)
|
setShowJumpPopover(prev => !prev)
|
||||||
}}
|
}}
|
||||||
@@ -165,10 +245,18 @@ export const SnsFilterPanel: React.FC<SnsFilterPanelProps> = ({
|
|||||||
isOpen={showJumpPopover}
|
isOpen={showJumpPopover}
|
||||||
currentDate={jumpPopoverDate}
|
currentDate={jumpPopoverDate}
|
||||||
onClose={() => setShowJumpPopover(false)}
|
onClose={() => setShowJumpPopover(false)}
|
||||||
|
onMonthChange={(date) => {
|
||||||
|
setJumpPopoverDate(date)
|
||||||
|
void loadJumpDateCounts(date)
|
||||||
|
}}
|
||||||
onSelect={(date) => {
|
onSelect={(date) => {
|
||||||
setJumpPopoverDate(date)
|
setJumpPopoverDate(date)
|
||||||
setJumpTargetDate(date)
|
setJumpTargetDate(date)
|
||||||
}}
|
}}
|
||||||
|
messageDates={jumpDateMessageDates}
|
||||||
|
hasLoadedMessageDates={hasLoadedJumpDateCounts}
|
||||||
|
messageDateCounts={jumpDateCounts}
|
||||||
|
loadingDateCounts={loadingJumpDateCounts}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user