diff --git a/electron/main.ts b/electron/main.ts index 718579f..2e3fbae 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -951,6 +951,10 @@ function registerIpcHandlers() { return snsService.getTimeline(limit, offset, usernames, keyword, startTime, endTime) }) + ipcMain.handle('sns:getSnsUsernames', async () => { + return snsService.getSnsUsernames() + }) + ipcMain.handle('sns:debugResource', async (_, url: string) => { return snsService.debugResource(url) }) diff --git a/electron/preload.ts b/electron/preload.ts index 3adee1c..cd6ce13 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -276,6 +276,7 @@ contextBridge.exposeInMainWorld('electronAPI', { sns: { getTimeline: (limit: number, offset: number, usernames?: string[], keyword?: string, startTime?: number, endTime?: number) => ipcRenderer.invoke('sns:getTimeline', limit, offset, usernames, keyword, startTime, endTime), + getSnsUsernames: () => ipcRenderer.invoke('sns:getSnsUsernames'), debugResource: (url: string) => ipcRenderer.invoke('sns:debugResource', url), proxyImage: (payload: { url: string; key?: string | number }) => ipcRenderer.invoke('sns:proxyImage', payload), downloadImage: (payload: { url: string; key?: string | number }) => ipcRenderer.invoke('sns:downloadImage', payload), diff --git a/electron/services/snsService.ts b/electron/services/snsService.ts index 0b468ca..ffc1c23 100644 --- a/electron/services/snsService.ts +++ b/electron/services/snsService.ts @@ -147,6 +147,18 @@ class SnsService { return join(this.getSnsCacheDir(), `${hash}${ext}`) } + // 获取所有发过朋友圈的用户名列表 + async getSnsUsernames(): Promise<{ success: boolean; usernames?: string[]; error?: string }> { + const result = await wcdbService.execQuery('sns', null, 'SELECT DISTINCT user_name FROM SnsTimeLine') + if (!result.success || !result.rows) { + // 尝试 userName 列名 + const result2 = await wcdbService.execQuery('sns', null, 'SELECT DISTINCT userName FROM SnsTimeLine') + if (!result2.success || !result2.rows) return { success: false, error: result.error || result2.error } + return { success: true, usernames: result2.rows.map((r: any) => r.userName).filter(Boolean) } + } + return { success: true, usernames: result.rows.map((r: any) => r.user_name).filter(Boolean) } + } + async getTimeline(limit: number = 20, offset: number = 0, usernames?: string[], keyword?: string, startTime?: number, endTime?: number): Promise<{ success: boolean; timeline?: SnsPost[]; error?: string }> { const result = await wcdbService.getSnsTimeline(limit, offset, usernames, keyword, startTime, endTime) diff --git a/resources/wcdb_api.dll b/resources/wcdb_api.dll index ecce05d..297f9c3 100644 Binary files a/resources/wcdb_api.dll and b/resources/wcdb_api.dll differ diff --git a/src/App.tsx b/src/App.tsx index af5fcdf..60ea948 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -195,10 +195,12 @@ function App() { if (isNotificationWindow) return // Skip updates in notification window const removeUpdateListener = window.electronAPI?.app?.onUpdateAvailable?.((info: any) => { - // 发现新版本时自动打开更新弹窗 + // 发现新版本时保存更新信息,锁定状态下不弹窗,解锁后再显示 if (info) { setUpdateInfo({ ...info, hasUpdate: true }) - setShowUpdateDialog(true) + if (!useAppStore.getState().isLocked) { + setShowUpdateDialog(true) + } } }) const removeProgressListener = window.electronAPI?.app?.onDownloadProgress?.((progress: any) => { @@ -210,6 +212,13 @@ function App() { } }, [setUpdateInfo, setDownloadProgress, setShowUpdateDialog, isNotificationWindow]) + // 解锁后显示暂存的更新弹窗 + useEffect(() => { + if (!isLocked && updateInfo?.hasUpdate && !showUpdateDialog && !isDownloading) { + setShowUpdateDialog(true) + } + }, [isLocked]) + const handleUpdateNow = async () => { setShowUpdateDialog(false) setIsDownloading(true) diff --git a/src/components/DateRangePicker.scss b/src/components/DateRangePicker.scss index af9602f..e702c57 100644 --- a/src/components/DateRangePicker.scss +++ b/src/components/DateRangePicker.scss @@ -139,6 +139,18 @@ font-size: 14px; font-weight: 600; color: var(--text-primary); + + &.clickable { + cursor: pointer; + border-radius: 6px; + padding: 2px 8px; + transition: all 0.15s; + + &:hover { + background: var(--bg-hover); + color: var(--primary); + } + } } } @@ -212,4 +224,68 @@ padding-top: 12px; border-top: 1px solid var(--border-color); } + + .year-month-picker { + padding: 4px 0; + + .year-selector { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; + + .year-label { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); + } + + .nav-btn { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + border: none; + background: var(--bg-tertiary); + border-radius: 6px; + cursor: pointer; + color: var(--text-secondary); + transition: all 0.15s; + + &:hover { + background: var(--bg-hover); + color: var(--text-primary); + } + } + } + + .month-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px; + + .month-btn { + padding: 8px 0; + border: none; + background: transparent; + border-radius: 8px; + cursor: pointer; + font-size: 13px; + color: var(--text-secondary); + transition: all 0.15s; + + &:hover { + background: var(--bg-hover); + color: var(--text-primary); + } + + &.active { + background: var(--primary); + color: #fff; + } + } + } + } } \ No newline at end of file diff --git a/src/components/DateRangePicker.tsx b/src/components/DateRangePicker.tsx index f88cfec..ea6f9b1 100644 --- a/src/components/DateRangePicker.tsx +++ b/src/components/DateRangePicker.tsx @@ -26,6 +26,7 @@ function DateRangePicker({ startDate, endDate, onStartDateChange, onEndDateChang const [isOpen, setIsOpen] = useState(false) const [currentMonth, setCurrentMonth] = useState(new Date()) const [selectingStart, setSelectingStart] = useState(true) + const [showYearMonthPicker, setShowYearMonthPicker] = useState(false) const containerRef = useRef(null) // 点击外部关闭 @@ -185,12 +186,38 @@ function DateRangePicker({ startDate, endDate, onStartDateChange, onEndDateChang - {currentMonth.getFullYear()}年 {MONTH_NAMES[currentMonth.getMonth()]} + setShowYearMonthPicker(!showYearMonthPicker)}> + {currentMonth.getFullYear()}年 {MONTH_NAMES[currentMonth.getMonth()]} + - {renderCalendar()} + {showYearMonthPicker ? ( +
+
+ + {currentMonth.getFullYear()}年 + +
+
+ {MONTH_NAMES.map((name, i) => ( + + ))} +
+
+ ) : renderCalendar()}
{selectingStart ? '请选择开始日期' : '请选择结束日期'}
diff --git a/src/components/JumpToDateDialog.scss b/src/components/JumpToDateDialog.scss index a4f0c51..5e03962 100644 --- a/src/components/JumpToDateDialog.scss +++ b/src/components/JumpToDateDialog.scss @@ -75,6 +75,18 @@ font-size: 15px; font-weight: 600; color: var(--text-primary); + + &.clickable { + cursor: pointer; + border-radius: 6px; + padding: 2px 8px; + transition: all 0.15s; + + &:hover { + background: var(--bg-hover); + color: var(--primary); + } + } } .nav-btn { @@ -97,6 +109,70 @@ } } } + + .year-month-picker { + padding: 4px 0; + + .year-selector { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; + + .year-label { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); + } + + .nav-btn { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s; + + &:hover { + background: var(--bg-hover); + border-color: var(--primary); + color: var(--primary); + } + } + } + + .month-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px; + + .month-btn { + padding: 10px 0; + border: none; + background: transparent; + border-radius: 8px; + cursor: pointer; + font-size: 13px; + color: var(--text-secondary); + transition: all 0.15s; + + &:hover { + background: var(--bg-hover); + color: var(--text-primary); + } + + &.active { + background: var(--primary); + color: #fff; + } + } + } + } } .calendar-grid { diff --git a/src/components/JumpToDateDialog.tsx b/src/components/JumpToDateDialog.tsx index ea68889..90044b2 100644 --- a/src/components/JumpToDateDialog.tsx +++ b/src/components/JumpToDateDialog.tsx @@ -24,6 +24,7 @@ const JumpToDateDialog: React.FC = ({ const isValidDate = (d: any) => d instanceof Date && !isNaN(d.getTime()) const [calendarDate, setCalendarDate] = useState(isValidDate(currentDate) ? new Date(currentDate) : new Date()) const [selectedDate, setSelectedDate] = useState(new Date(currentDate)) + const [showYearMonthPicker, setShowYearMonthPicker] = useState(false) if (!isOpen) return null @@ -137,7 +138,7 @@ const JumpToDateDialog: React.FC = ({ > - + setShowYearMonthPicker(!showYearMonthPicker)}> {calendarDate.getFullYear()}年{calendarDate.getMonth() + 1}月 + {showYearMonthPicker ? ( +
+
+ + {calendarDate.getFullYear()}年 + +
+
+ {['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'].map((name, i) => ( + + ))} +
+
+ ) : (
{loadingDates && (
@@ -174,6 +200,7 @@ const JumpToDateDialog: React.FC = ({ ))}
+ )}
diff --git a/src/components/Sns/SnsFilterPanel.tsx b/src/components/Sns/SnsFilterPanel.tsx index 6182d88..9894689 100644 --- a/src/components/Sns/SnsFilterPanel.tsx +++ b/src/components/Sns/SnsFilterPanel.tsx @@ -138,7 +138,7 @@ export const SnsFilterPanel: React.FC = ({ /> {contactSearch && ( - setContactSearch('')} style={{ right: 8, top: 8, position: 'absolute', cursor: 'pointer', color: 'var(--text-tertiary)' }} /> + setContactSearch('')} /> )}
diff --git a/src/pages/ExportPage.scss b/src/pages/ExportPage.scss index c459be1..1d7e414 100644 --- a/src/pages/ExportPage.scss +++ b/src/pages/ExportPage.scss @@ -955,6 +955,18 @@ font-size: 15px; font-weight: 600; color: var(--text-primary); + + &.clickable { + cursor: pointer; + border-radius: 6px; + padding: 2px 8px; + transition: all 0.15s; + + &:hover { + background: var(--bg-hover); + color: var(--primary); + } + } } } @@ -1015,6 +1027,70 @@ } } + .year-month-picker { + padding: 4px 0; + + .year-selector { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; + + .year-label { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); + } + + .calendar-nav-btn { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + cursor: pointer; + color: var(--text-secondary); + transition: all 0.2s; + + &:hover { + background: var(--bg-hover); + border-color: var(--primary); + color: var(--primary); + } + } + } + + .month-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px; + + .month-btn { + padding: 10px 0; + border: none; + background: transparent; + border-radius: 8px; + cursor: pointer; + font-size: 13px; + color: var(--text-secondary); + transition: all 0.15s; + + &:hover { + background: var(--bg-hover); + color: var(--text-primary); + } + + &.active { + background: var(--primary); + color: #fff; + } + } + } + } + .date-picker-actions { display: flex; gap: 12px; diff --git a/src/pages/ExportPage.tsx b/src/pages/ExportPage.tsx index 2211143..fce60b4 100644 --- a/src/pages/ExportPage.tsx +++ b/src/pages/ExportPage.tsx @@ -53,6 +53,7 @@ function ExportPage() { const [showDatePicker, setShowDatePicker] = useState(false) const [calendarDate, setCalendarDate] = useState(new Date()) const [selectingStart, setSelectingStart] = useState(true) + const [showYearMonthPicker, setShowYearMonthPicker] = useState(false) const [showMediaLayoutPrompt, setShowMediaLayoutPrompt] = useState(false) const [showDisplayNameSelect, setShowDisplayNameSelect] = useState(false) const [showPreExportDialog, setShowPreExportDialog] = useState(false) @@ -1047,7 +1048,7 @@ function ExportPage() { {/* 日期选择弹窗 */} {showDatePicker && ( -
setShowDatePicker(false)}> +
{ setShowDatePicker(false); setShowYearMonthPicker(false) }}>
e.stopPropagation()}>

选择时间范围

@@ -1122,7 +1123,7 @@ function ExportPage() { > - + setShowYearMonthPicker(!showYearMonthPicker)}> {calendarDate.getFullYear()}年{calendarDate.getMonth() + 1}月

+ {showYearMonthPicker ? ( +
+
+ + {calendarDate.getFullYear()}年 + +
+
+ {['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'].map((name, i) => ( + + ))} +
+
+ ) : ( + <>
{['日', '一', '二', '三', '四', '五', '六'].map(day => (
{day}
@@ -1163,12 +1190,14 @@ function ExportPage() { ) })}
+ + )}
- -
diff --git a/src/pages/SnsPage.scss b/src/pages/SnsPage.scss index badd3ed..39d0726 100644 --- a/src/pages/SnsPage.scss +++ b/src/pages/SnsPage.scss @@ -24,8 +24,6 @@ .sns-main-viewport { flex: 1; overflow-y: scroll; - /* Always show scrollbar track for stability */ - scroll-behavior: smooth; position: relative; display: flex; justify-content: center; @@ -106,6 +104,7 @@ transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02); position: relative; + overflow: hidden; &:hover { transform: translateY(-2px); @@ -148,6 +147,8 @@ .post-author-info { display: flex; flex-direction: column; + min-width: 0; + overflow: hidden; .author-name { font-size: 15px; @@ -168,6 +169,7 @@ display: flex; align-items: center; gap: 6px; + flex-shrink: 0; } .debug-btn { @@ -694,6 +696,17 @@ top: 8px; color: var(--text-tertiary); pointer-events: none; + display: none; + } + + .clear-icon { + position: absolute; + right: 28px; + top: 8px; + color: var(--text-tertiary); + cursor: pointer; + display: flex; + align-items: center; } } @@ -1309,6 +1322,18 @@ font-size: 15px; font-weight: 600; color: var(--text-primary); + + &.clickable { + cursor: pointer; + border-radius: 6px; + padding: 2px 8px; + transition: all 0.15s; + + &:hover { + background: var(--bg-hover); + color: var(--primary); + } + } } .nav-btn { @@ -1384,6 +1409,70 @@ } } } + + .year-month-picker { + padding: 4px 0; + + .year-selector { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; + + .year-label { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); + } + + .nav-btn { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s; + + &:hover { + background: var(--bg-hover); + border-color: var(--primary); + color: var(--primary); + } + } + } + + .month-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px; + + .month-btn { + padding: 10px 0; + border: none; + background: transparent; + border-radius: 8px; + cursor: pointer; + font-size: 13px; + color: var(--text-secondary); + transition: all 0.15s; + + &:hover { + background: var(--bg-hover); + color: var(--text-primary); + } + + &.active { + background: var(--primary); + color: #fff; + } + } + } + } } .quick-options { diff --git a/src/pages/SnsPage.tsx b/src/pages/SnsPage.tsx index ae30e01..51dc012 100644 --- a/src/pages/SnsPage.tsx +++ b/src/pages/SnsPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useRef, useCallback } from 'react' +import { useEffect, useLayoutEffect, useState, useRef, useCallback } from 'react' import { RefreshCw, Search, X, Download, FolderOpen, FileJson, FileText, Image, CheckCircle, AlertCircle, Calendar, Users, Info, ChevronLeft, ChevronRight } from 'lucide-react' import { ImagePreview } from '../components/ImagePreview' import JumpToDateDialog from '../components/JumpToDateDialog' @@ -11,6 +11,7 @@ interface Contact { username: string displayName: string avatarUrl?: string + type?: 'friend' | 'former_friend' | 'sns_only' } export default function SnsPage() { @@ -45,28 +46,29 @@ export default function SnsPage() { const [exportResult, setExportResult] = useState<{ success: boolean; filePath?: string; postCount?: number; mediaCount?: number; error?: string } | null>(null) const [refreshSpin, setRefreshSpin] = useState(false) const [calendarPicker, setCalendarPicker] = useState<{ field: 'start' | 'end'; month: Date } | null>(null) + const [showYearMonthPicker, setShowYearMonthPicker] = useState(false) const postsContainerRef = useRef(null) const [hasNewer, setHasNewer] = useState(false) const [loadingNewer, setLoadingNewer] = useState(false) const postsRef = useRef([]) - const scrollAdjustmentRef = useRef(0) + const scrollAdjustmentRef = useRef<{ scrollHeight: number; scrollTop: number } | null>(null) // Sync posts ref useEffect(() => { postsRef.current = posts }, [posts]) - // Maintain scroll position when loading newer posts - useEffect(() => { - if (scrollAdjustmentRef.current !== 0 && postsContainerRef.current) { + // 在 DOM 更新后、浏览器绘制前同步调整滚动位置,防止向上加载时页面跳动 + useLayoutEffect(() => { + const snapshot = scrollAdjustmentRef.current; + if (snapshot && postsContainerRef.current) { const container = postsContainerRef.current; - const newHeight = container.scrollHeight; - const diff = newHeight - scrollAdjustmentRef.current; - if (diff > 0) { - container.scrollTop += diff; + const addedHeight = container.scrollHeight - snapshot.scrollHeight; + if (addedHeight > 0) { + container.scrollTop = snapshot.scrollTop + addedHeight; } - scrollAdjustmentRef.current = 0; + scrollAdjustmentRef.current = null; } }, [posts]) @@ -104,14 +106,17 @@ export default function SnsPage() { if (result.success && result.timeline && result.timeline.length > 0) { if (postsContainerRef.current) { - scrollAdjustmentRef.current = postsContainerRef.current.scrollHeight; + scrollAdjustmentRef.current = { + scrollHeight: postsContainerRef.current.scrollHeight, + scrollTop: postsContainerRef.current.scrollTop + }; } const existingIds = new Set(currentPosts.map((p: SnsPost) => p.id)); const uniqueNewer = result.timeline.filter((p: SnsPost) => !existingIds.has(p.id)); if (uniqueNewer.length > 0) { - setPosts(prev => [...uniqueNewer, ...prev]); + setPosts(prev => [...uniqueNewer, ...prev].sort((a, b) => b.createTime - a.createTime)); } setHasNewer(result.timeline.length >= limit); } else { @@ -157,7 +162,7 @@ export default function SnsPage() { } } else { if (result.timeline.length > 0) { - setPosts(prev => [...prev, ...result.timeline!]) + setPosts(prev => [...prev, ...result.timeline!].sort((a, b) => b.createTime - a.createTime)) } if (result.timeline.length < limit) { setHasMore(false) @@ -173,45 +178,59 @@ export default function SnsPage() { } }, [selectedUsernames, searchKeyword, jumpTargetDate]) - // Load Contacts + // Load Contacts(合并好友+曾经好友+朋友圈发布者,enrichSessionsContactInfo 补充头像) const loadContacts = useCallback(async () => { setContactsLoading(true) try { - const result = await window.electronAPI.chat.getSessions() - if (result.success && result.sessions) { - const systemAccounts = ['filehelper', 'fmessage', 'newsapp', 'weixin', 'qqmail', 'tmessage', 'floatbottle', 'medianote', 'brandsessionholder']; - const initialContacts = result.sessions - .filter((s: any) => { - if (!s.username) return false; - const u = s.username.toLowerCase(); - if (u.includes('@chatroom') || u.endsWith('@chatroom') || u.endsWith('@openim')) return false; - if (u.startsWith('gh_')) return false; - if (systemAccounts.includes(u) || u.includes('helper') || u.includes('sessionholder')) return false; - return true; - }) - .map((s: any) => ({ - username: s.username, - displayName: s.displayName || s.username, - avatarUrl: s.avatarUrl - })) - setContacts(initialContacts) + // 并行获取联系人列表和朋友圈发布者列表 + const [contactsResult, snsResult] = await Promise.all([ + window.electronAPI.chat.getContacts(), + window.electronAPI.sns.getSnsUsernames() + ]) - const usernames = initialContacts.map((c: { username: string }) => c.username) - const enriched = await window.electronAPI.chat.enrichSessionsContactInfo(usernames) - if (enriched.success && enriched.contacts) { - setContacts(prev => prev.map(c => { - const extra = enriched.contacts![c.username] - if (extra) { - return { - ...c, - displayName: extra.displayName || c.displayName, - avatarUrl: extra.avatarUrl || c.avatarUrl - } - } - return c - })) + // 以联系人为基础,按 username 去重 + const contactMap = new Map() + + // 好友和曾经的好友 + if (contactsResult.success && contactsResult.contacts) { + for (const c of contactsResult.contacts) { + if (c.type === 'friend' || c.type === 'former_friend') { + contactMap.set(c.username, { + username: c.username, + displayName: c.displayName, + avatarUrl: c.avatarUrl, + type: c.type === 'former_friend' ? 'former_friend' : 'friend' + }) + } } } + + // 朋友圈发布者(补充不在联系人列表中的用户) + if (snsResult.success && snsResult.usernames) { + for (const u of snsResult.usernames) { + if (!contactMap.has(u)) { + contactMap.set(u, { username: u, displayName: u, type: 'sns_only' }) + } + } + } + + const allUsernames = Array.from(contactMap.keys()) + + // 用 enrichSessionsContactInfo 统一补充头像和显示名 + if (allUsernames.length > 0) { + const enriched = await window.electronAPI.chat.enrichSessionsContactInfo(allUsernames) + if (enriched.success && enriched.contacts) { + for (const [username, extra] of Object.entries(enriched.contacts) as [string, { displayName?: string; avatarUrl?: string }][]) { + const c = contactMap.get(username) + if (c) { + c.displayName = extra.displayName || c.displayName + c.avatarUrl = extra.avatarUrl || c.avatarUrl + } + } + } + } + + setContacts(Array.from(contactMap.values())) } catch (error) { console.error('Failed to load contacts:', error) } finally { @@ -336,7 +355,12 @@ export default function SnsPage() { )} {!hasMore && posts.length > 0 && ( -
已经到底啦
+
{ + selectedUsernames.length === 1 && + contacts.find(c => c.username === selectedUsernames[0])?.type === 'former_friend' + ? '在时间的长河里刻舟求剑' + : '或许过往已无可溯洄,但好在还有可以与你相遇的明天' + }
)} {!loading && posts.length === 0 && ( @@ -655,14 +679,14 @@ export default function SnsPage() { {/* 日期选择弹窗 */} {calendarPicker && ( -
setCalendarPicker(null)}> +
{ setCalendarPicker(null); setShowYearMonthPicker(false) }}>
e.stopPropagation()}>

选择{calendarPicker.field === 'start' ? '开始' : '结束'}日期

-
@@ -671,13 +695,39 @@ export default function SnsPage() { - + setShowYearMonthPicker(!showYearMonthPicker)}> {calendarPicker.month.getFullYear()}年{calendarPicker.month.getMonth() + 1}月
+ {showYearMonthPicker ? ( +
+
+ + {calendarPicker.month.getFullYear()}年 + +
+
+ {['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'].map((name, i) => ( + + ))} +
+
+ ) : ( + <>
{['日', '一', '二', '三', '四', '五', '六'].map(d =>
{d}
)}
@@ -710,6 +760,8 @@ export default function SnsPage() { }) })()}
+ + )}
- +
diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index 2863202..95439b8 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -503,6 +503,7 @@ export interface ElectronAPI { }) => Promise<{ success: boolean; filePath?: string; postCount?: number; mediaCount?: number; error?: string }> onExportProgress: (callback: (payload: { current: number; total: number; status: string }) => void) => () => void selectExportDir: () => Promise<{ canceled: boolean; filePath?: string }> + getSnsUsernames: () => Promise<{ success: boolean; usernames?: string[]; error?: string }> } llama: { loadModel: (modelPath: string) => Promise diff --git a/src/types/models.ts b/src/types/models.ts index 986a694..5b5a3bf 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -32,7 +32,7 @@ export interface ContactInfo { remark?: string nickname?: string avatarUrl?: string - type: 'friend' | 'group' | 'official' | 'other' + type: 'friend' | 'group' | 'official' | 'former_friend' | 'other' } // 消息