From 531915387921af0dc0ce790d417f6e8121c94312 Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 5 May 2026 20:24:58 +0800 Subject: [PATCH] refactor: streamline sns page --- .../Sns/ContactSnsTimelineDialog.scss | 67 ++- .../Sns/ContactSnsTimelineDialog.tsx | 4 - src/components/Sns/SnsFilterPanel.tsx | 119 ++--- src/components/Sns/SnsPostItem.tsx | 2 +- src/pages/SnsPage.scss | 423 +++++++++--------- src/pages/SnsPage.tsx | 142 +++--- 6 files changed, 396 insertions(+), 361 deletions(-) diff --git a/src/components/Sns/ContactSnsTimelineDialog.scss b/src/components/Sns/ContactSnsTimelineDialog.scss index dd45cd0..b8e1c20 100644 --- a/src/components/Sns/ContactSnsTimelineDialog.scss +++ b/src/components/Sns/ContactSnsTimelineDialog.scss @@ -6,16 +6,16 @@ align-items: center; justify-content: center; padding: 24px 16px; - background: rgba(15, 23, 42, 0.38); + background: rgba(15, 23, 42, 0.28); } .contact-sns-dialog { - width: min(760px, 100%); - max-height: min(86vh, 860px); - border-radius: 14px; + width: min(720px, 100%); + max-height: min(84vh, 820px); + border-radius: 10px; border: 1px solid var(--border-color); background: var(--bg-secondary-solid, #ffffff); - box-shadow: 0 22px 46px rgba(0, 0, 0, 0.24); + box-shadow: 0 18px 34px rgba(0, 0, 0, 0.18); display: flex; flex-direction: column; overflow: hidden; @@ -29,7 +29,7 @@ align-items: center; justify-content: space-between; gap: 10px; - padding: 14px 16px; + padding: 12px 14px; border-bottom: 1px solid var(--border-color); } @@ -41,9 +41,9 @@ } .contact-sns-dialog-avatar { - width: 42px; - height: 42px; - border-radius: 10px; + width: 36px; + height: 36px; + border-radius: 8px; background: linear-gradient(135deg, var(--primary), var(--primary-hover)); overflow: hidden; flex-shrink: 0; @@ -69,7 +69,7 @@ h4 { margin: 0; - font-size: 15px; + font-size: 14px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; @@ -79,7 +79,7 @@ .contact-sns-dialog-username { margin-top: 2px; - font-size: 12px; + font-size: 11px; color: var(--text-tertiary); overflow: hidden; text-overflow: ellipsis; @@ -88,7 +88,7 @@ .contact-sns-dialog-stats { margin-top: 4px; - font-size: 12px; + font-size: 11px; color: var(--text-secondary); } @@ -111,9 +111,9 @@ border-radius: 8px; background: var(--bg-primary); color: var(--text-secondary); - height: 28px; - padding: 0 10px; - font-size: 12px; + height: 30px; + padding: 0 9px; + font-size: 11px; line-height: 1; cursor: pointer; white-space: nowrap; @@ -134,8 +134,8 @@ position: absolute; top: calc(100% + 8px); right: 0; - width: 248px; - max-height: calc((28px * 15) + 16px); + width: 228px; + max-height: calc((26px * 15) + 16px); overflow-y: auto; border: 1px solid color-mix(in srgb, var(--primary) 30%, var(--border-color)); border-radius: 10px; @@ -220,26 +220,20 @@ } .contact-sns-dialog-tip { - padding: 10px 16px; - border-bottom: 1px solid color-mix(in srgb, var(--border-color) 88%, transparent); - background: color-mix(in srgb, var(--bg-primary) 78%, var(--bg-secondary)); - font-size: 12px; - line-height: 1.6; - color: var(--text-secondary); - word-break: break-word; + display: none; } .contact-sns-dialog-body { flex: 1; min-height: 0; overflow-y: auto; - padding: 12px 16px 14px; + padding: 10px 12px 12px; } .contact-sns-dialog-posts-list { display: flex; flex-direction: column; - gap: 14px; + gap: 10px; } .contact-sns-dialog-posts-list .post-header-actions { @@ -247,9 +241,9 @@ } .contact-sns-dialog-status { - padding: 20px 12px; + padding: 16px 10px; text-align: center; - font-size: 13px; + font-size: 12px; color: var(--text-secondary); &.empty { @@ -264,8 +258,8 @@ background: var(--bg-primary); color: var(--text-primary); border-radius: 10px; - padding: 9px 18px; - font-size: 13px; + padding: 8px 14px; + font-size: 12px; cursor: pointer; &:hover:not(:disabled) { @@ -282,15 +276,15 @@ @media (max-width: 768px) { .contact-sns-dialog-overlay { - padding: 12px 8px; + padding: 10px 8px; } .contact-sns-dialog { - width: min(100vw - 16px, 760px); + width: min(100vw - 16px, 720px); max-height: calc(100vh - 24px); .contact-sns-dialog-header { - padding: 12px; + padding: 10px 12px; } .contact-sns-dialog-header-actions { @@ -300,18 +294,13 @@ .contact-sns-dialog-rank-btn { height: 26px; padding: 0 8px; - font-size: 11px; + font-size: 10px; } .contact-sns-dialog-rank-panel { width: min(78vw, 232px); } - .contact-sns-dialog-tip { - padding: 10px 12px; - line-height: 1.55; - } - .contact-sns-dialog-body { padding: 10px 10px 12px; } diff --git a/src/components/Sns/ContactSnsTimelineDialog.tsx b/src/components/Sns/ContactSnsTimelineDialog.tsx index 3547954..3ab1280 100644 --- a/src/components/Sns/ContactSnsTimelineDialog.tsx +++ b/src/components/Sns/ContactSnsTimelineDialog.tsx @@ -538,10 +538,6 @@ export function ContactSnsTimelineDialog({ -
- 在微信桌面客户端中打开这个人的朋友圈浏览,可快速把其朋友圈同步到这里。若你在乎这个人,一定要试试~ -
-
= ({ onClearSelectedContacts, onExportSelectedContacts }) => { - const filteredContacts = contacts.filter(c => - (c.displayName || '').toLowerCase().includes(contactSearch.toLowerCase()) || - c.username.toLowerCase().includes(contactSearch.toLowerCase()) - ) + const filteredContacts = React.useMemo(() => { + const keyword = contactSearch.trim().toLowerCase() + if (!keyword) return contacts + return contacts.filter(c => + (c.displayName || '').toLowerCase().includes(keyword) || + c.username.toLowerCase().includes(keyword) + ) + }, [contacts, contactSearch]) const selectedContactLookup = React.useMemo( () => new Set(selectedContactUsernames), [selectedContactUsernames] @@ -85,10 +90,52 @@ export const SnsFilterPanel: React.FC = ({ return '没有找到联系人' } + const renderContactRow = React.useCallback((_: number, contact: Contact) => { + const isPostCountReady = contact.postCountStatus === 'ready' + const isSelected = selectedContactLookup.has(contact.username) + const isActive = activeContactUsername === contact.username + + return ( +
+ + +
+ ) + }, [activeContactUsername, onOpenContactTimeline, onToggleContactSelected, selectedContactLookup]) + return (
diff --git a/src/components/Sns/SnsPostItem.tsx b/src/components/Sns/SnsPostItem.tsx index adb7be1..e448b3f 100644 --- a/src/components/Sns/SnsPostItem.tsx +++ b/src/components/Sns/SnsPostItem.tsx @@ -493,7 +493,7 @@ export const SnsPostItem: React.FC = ({ post, onPreview, onDeb diff --git a/src/pages/SnsPage.scss b/src/pages/SnsPage.scss index cc1b0bc..fa66895 100644 --- a/src/pages/SnsPage.scss +++ b/src/pages/SnsPage.scss @@ -1,12 +1,13 @@ /* Global Variables */ :root { - --sns-max-width: 800px; - --sns-panel-width: 380px; + --sns-max-width: 920px; + --sns-panel-width: 320px; --sns-bg-color: var(--bg-primary); - --sns-card-bg: var(--bg-secondary); - --sns-border-radius-lg: 16px; - --sns-border-radius-md: 12px; - --sns-border-radius-sm: 8px; + --sns-card-bg: transparent; + --sns-border-radius-lg: 10px; + --sns-border-radius-md: 8px; + --sns-border-radius-sm: 6px; + --sns-media-cell: 88px; } .sns-page-layout { @@ -33,7 +34,7 @@ .sns-feed-container { width: 100%; max-width: var(--sns-max-width); - padding: 10px 24px 12px 24px; + padding: 14px 20px 0; display: flex; flex-direction: column; gap: 0; @@ -45,13 +46,12 @@ display: flex; align-items: center; justify-content: space-between; - margin-bottom: 4px; - padding: 0 4px; + gap: 16px; + margin-bottom: 0; + padding: 2px 2px 10px; z-index: 2; background: var(--sns-bg-color); border-bottom: 1px solid var(--border-color); - padding-top: 4px; - padding-bottom: 6px; .feed-header-main { display: flex; @@ -70,9 +70,9 @@ .feed-stats-line { display: flex; align-items: center; - gap: 8px; + gap: 6px; flex-wrap: wrap; - font-size: 13px; + font-size: 12px; color: var(--text-secondary); line-height: 1.4; @@ -170,7 +170,7 @@ .header-actions { display: flex; align-items: center; - gap: 10px; + gap: 6px; } .jump-calendar-anchor { @@ -186,24 +186,28 @@ } .icon-btn { - background: var(--bg-tertiary); + width: 32px; + height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + background: transparent; border: 1px solid var(--border-color); border-radius: var(--sns-border-radius-sm); - padding: 8px; + padding: 0; color: var(--text-secondary); cursor: pointer; - transition: all 0.2s; + transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease; &:hover { - background: var(--hover-bg); - color: var(--primary); - transform: scale(1.05); + background: var(--bg-hover); + border-color: color-mix(in srgb, var(--text-tertiary) 34%, var(--border-color)); + color: var(--text-primary); } &:disabled { opacity: 0.5; cursor: not-allowed; - transform: none; } .spinning { @@ -217,20 +221,21 @@ gap: 8px; border: 1px solid var(--border-color); border-radius: var(--sns-border-radius-sm); - background: var(--bg-tertiary); + background: transparent; color: var(--text-secondary); - padding: 8px 10px; + min-height: 32px; + padding: 0 9px; cursor: pointer; - transition: all 0.2s; + transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease; &:hover { - background: var(--hover-bg); - color: var(--primary); + background: var(--bg-hover); + color: var(--text-primary); } &.active { border-color: var(--primary); - background: rgba(var(--primary-rgb), 0.08); + background: rgba(var(--primary-rgb), 0.07); color: var(--primary); } } @@ -256,19 +261,28 @@ } } -.sns-posts-scroll { +.sns-posts-stage { flex: 1; min-height: 0; + position: relative; +} + +.sns-posts-scroll { + height: 100%; overflow-y: auto; - padding-top: 16px; + padding-top: 8px; +} + +.sns-post-row { + padding-bottom: 10px; } .feed-contact-filter-bar { - margin: 10px 4px 0; - padding: 10px 12px; - border: 1px solid color-mix(in srgb, var(--primary) 28%, var(--border-color)); - border-radius: 12px; - background: rgba(var(--primary-rgb), 0.08); + margin: 8px 0 0; + padding: 7px 9px; + border: 1px solid color-mix(in srgb, var(--primary) 20%, var(--border-color)); + border-radius: var(--sns-border-radius-md); + background: rgba(var(--primary-rgb), 0.06); display: flex; align-items: center; gap: 8px; @@ -281,7 +295,7 @@ } .feed-contact-filter-summary { - font-size: 13px; + font-size: 12px; color: var(--text-primary); font-weight: 600; min-width: 0; @@ -293,7 +307,7 @@ background: transparent; color: var(--primary); font-size: 12px; - font-weight: 600; + font-weight: 500; cursor: pointer; padding: 0; white-space: nowrap; @@ -308,7 +322,7 @@ .posts-list { display: flex; flex-direction: column; - gap: 24px; + gap: 10px; } /* ========================================= @@ -316,19 +330,21 @@ ========================================= */ .sns-post-item { background: var(--sns-card-bg); - border-radius: var(--sns-border-radius-lg); - border: 1px solid var(--border-color); - padding: 20px; + border-radius: 0; + border: none; + border-bottom: 1px solid color-mix(in srgb, var(--border-color) 82%, transparent); + padding: 10px 6px 12px; display: flex; - gap: 16px; - 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); + gap: 12px; + transition: background 0.15s ease; + box-shadow: none; position: relative; - overflow: hidden; + overflow: visible; &:hover { - transform: translateY(-2px); - box-shadow: 0 8px 16px rgba(0, 0, 0, 0.06); + background: color-mix(in srgb, var(--bg-secondary) 72%, transparent); + box-shadow: none; + transform: none; } &.post-deleted { @@ -341,9 +357,9 @@ gap: 4px; background: rgba(255, 77, 79, 0.1); color: #ff4d4f; - font-size: 12px; + font-size: 11px; font-weight: 500; - padding: 3px 8px; + padding: 2px 6px; border-radius: 6px; } } @@ -362,12 +378,12 @@ } .avatar-trigger { - border-radius: 12px; - transition: transform 0.15s ease, box-shadow 0.15s ease; + border-radius: var(--sns-border-radius-md); + transition: opacity 0.15s ease; &:hover { - transform: translateY(-1px); - box-shadow: 0 6px 14px rgba(0, 0, 0, 0.1); + opacity: 0.86; + box-shadow: none; } &:focus-visible { @@ -386,7 +402,8 @@ display: flex; justify-content: space-between; align-items: flex-start; - margin-bottom: 8px; + gap: 10px; + margin-bottom: 5px; .post-author-info { display: flex; @@ -395,12 +412,10 @@ overflow: hidden; .author-name { - font-size: 15px; - font-weight: 700; - color: var(--text-primary); - /* Changed to primary from accent for cleaner look, or keep accent */ + font-size: 14px; + font-weight: 600; color: var(--primary); - margin-bottom: 2px; + margin-bottom: 1px; } .author-name-trigger { @@ -430,13 +445,14 @@ .post-time { font-size: 12px; color: var(--text-tertiary); + line-height: 1.25; } } .post-header-actions { display: flex; align-items: center; - gap: 6px; + gap: 4px; flex-shrink: 0; } @@ -451,9 +467,11 @@ opacity: 0; transition: opacity 0.2s; background: none; - border: 1px solid var(--border-color); + border: none; border-radius: 6px; - padding: 6px; + width: 24px; + height: 24px; + padding: 0; color: var(--text-tertiary); cursor: pointer; display: flex; @@ -462,8 +480,7 @@ &:hover { color: var(--text-primary); - background: var(--bg-tertiary); - border-color: var(--text-secondary); + background: var(--bg-hover); } &.delete-btn:hover { @@ -751,20 +768,20 @@ } .post-text { - font-size: 15px; - line-height: 1.6; + font-size: 14px; + line-height: 1.48; color: var(--text-primary); white-space: pre-wrap; word-break: break-word; - margin-bottom: 12px; + margin: 0 0 8px; } .post-location { display: flex; align-items: flex-start; gap: 6px; - margin: -4px 0 12px; - font-size: 13px; + margin: -2px 0 8px; + font-size: 12px; line-height: 1.45; color: var(--text-secondary); @@ -780,7 +797,7 @@ } .post-media-container { - margin-bottom: 12px; + margin-bottom: 8px; } .post-link-card { @@ -788,14 +805,14 @@ background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: var(--sns-border-radius-md); - padding: 10px; + padding: 8px; display: flex; align-items: center; - gap: 12px; + gap: 10px; cursor: pointer; text-align: left; - transition: all 0.2s; - margin-bottom: 12px; + transition: background 0.15s ease, border-color 0.15s ease; + margin-bottom: 8px; &:hover { background: rgba(var(--primary-rgb), 0.08); @@ -803,8 +820,8 @@ } .link-thumb { - width: 60px; - height: 60px; + width: 48px; + height: 48px; border-radius: var(--sns-border-radius-sm); overflow: hidden; flex-shrink: 0; @@ -830,7 +847,7 @@ min-width: 0; .link-title { - font-size: 14px; + font-size: 13px; font-weight: 600; color: var(--text-primary); overflow: hidden; @@ -839,7 +856,7 @@ } .link-url { - font-size: 12px; + font-size: 11px; color: var(--text-tertiary); margin-top: 4px; } @@ -851,15 +868,15 @@ } .post-interactions { - margin-top: 12px; - padding-top: 12px; - border-top: 1px dashed var(--border-color); - font-size: 13px; + margin-top: 8px; + padding-top: 0; + border-top: none; + font-size: 12px; .likes-block { display: flex; - gap: 8px; - margin-bottom: 8px; + gap: 6px; + margin-bottom: 6px; color: var(--primary); font-weight: 500; @@ -871,11 +888,11 @@ .comments-block { background: var(--bg-tertiary); border-radius: var(--sns-border-radius-sm); - padding: 8px 12px; + padding: 6px 8px; .comment-row { - margin-bottom: 4px; - line-height: 1.4; + margin-bottom: 3px; + line-height: 1.45; color: var(--text-secondary); &:last-child { @@ -912,13 +929,13 @@ ========================================= */ .sns-media-grid { display: grid; - gap: 6px; + gap: 4px; &.grid-1 .sns-media-item { width: fit-content; height: auto; - max-width: 300px; - max-height: 480px; + max-width: 270px; + max-height: 340px; aspect-ratio: auto; border-radius: var(--sns-border-radius-md); @@ -930,7 +947,7 @@ img, video { max-width: 100%; - max-height: 480px; + max-height: 340px; width: auto; height: auto; object-fit: contain; @@ -941,26 +958,25 @@ &.grid-2, &.grid-4 { - grid-template-columns: repeat(2, 1fr); - max-width: 320px; + grid-template-columns: repeat(2, var(--sns-media-cell)); + max-width: calc((var(--sns-media-cell) * 2) + 4px); } &.grid-3, &.grid-6, &.grid-9 { - grid-template-columns: repeat(3, 1fr); - max-width: 320px; + grid-template-columns: repeat(3, var(--sns-media-cell)); + max-width: calc((var(--sns-media-cell) * 3) + 8px); } .sns-media-item { position: relative; aspect-ratio: 1; - border-radius: var(--sns-border-radius-md); - /* Consistent radius for grid items */ + border-radius: var(--sns-border-radius-sm); overflow: hidden; cursor: zoom-in; background: var(--bg-tertiary); - transition: opacity 0.2s; + transition: opacity 0.15s ease; &:hover { opacity: 0.9; @@ -972,7 +988,7 @@ height: 100%; object-fit: cover; display: block; - animation: fade-in 0.3s ease; + animation: fade-in 0.2s ease; } .media-badge { @@ -984,7 +1000,6 @@ height: 32px; background: rgba(0, 0, 0, 0.4); border-radius: 50%; - backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; @@ -993,8 +1008,8 @@ /* Let clicks pass through badge to item */ &.live { - top: 8px; - right: 8px; + top: 6px; + right: 6px; left: auto; transform: none; width: 24px; @@ -1015,30 +1030,27 @@ gap: 8px; z-index: 10; font-size: 12px; - backdrop-filter: blur(2px); } .media-download-btn { position: absolute; - bottom: 6px; - right: 6px; - width: 28px; - height: 28px; + bottom: 5px; + right: 5px; + width: 24px; + height: 24px; background: rgba(0, 0, 0, 0.5); border-radius: 50%; - backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; color: white; cursor: pointer; opacity: 0; - transition: all 0.2s; + transition: opacity 0.15s ease, background 0.15s ease; z-index: 5; &:hover { background: var(--primary); - transform: scale(1.1); } /* Increase click area */ @@ -1092,8 +1104,8 @@ background: var(--bg-secondary); display: flex; flex-direction: column; - padding: 24px; - gap: 24px; + padding: 16px 14px; + gap: 14px; z-index: 10; .filter-header { @@ -1102,8 +1114,8 @@ align-items: center; h3 { - font-size: 16px; - font-weight: 700; + font-size: 15px; + font-weight: 650; margin: 0; } @@ -1112,10 +1124,16 @@ border: none; cursor: pointer; color: var(--text-tertiary); + width: 28px; + height: 28px; + border-radius: var(--sns-border-radius-sm); + display: inline-flex; + align-items: center; + justify-content: center; &:hover { - color: var(--primary); - animation: spin 0.5s ease; + background: var(--bg-hover); + color: var(--text-primary); } } } @@ -1123,30 +1141,30 @@ .filter-widgets { display: flex; flex-direction: column; - gap: 20px; + gap: 14px; flex: 1; min-height: 0; } .filter-widget { - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--sns-border-radius-md); - padding: 16px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02); + background: transparent; + border: none; + border-radius: 0; + padding: 0; + box-shadow: none; .widget-header { display: flex; align-items: center; - gap: 8px; - margin-bottom: 12px; - font-size: 13px; + gap: 6px; + margin-bottom: 8px; + font-size: 12px; font-weight: 600; color: var(--text-secondary); svg { - color: var(--primary); - opacity: 0.8; + color: var(--text-tertiary); + opacity: 1; } .badge { @@ -1161,7 +1179,7 @@ .widget-header-summary { margin-left: auto; font-size: 12px; - font-weight: 500; + font-weight: 400; color: var(--text-tertiary); white-space: nowrap; } @@ -1183,17 +1201,16 @@ background: var(--bg-tertiary); border: 1px solid transparent; border-radius: var(--sns-border-radius-sm); - padding: 10px 30px 10px 12px; + height: 34px; + padding: 0 30px 0 10px; font-size: 13px; color: var(--text-primary); - transition: all 0.2s; + transition: background 0.15s ease, border-color 0.15s ease; &:focus { - background: var(--bg-primary); + background: var(--bg-secondary); border-color: transparent; - /* Explicitly transparent */ outline: none; - /* Ensure no outline */ box-shadow: none; } } @@ -1218,14 +1235,16 @@ min-height: 300px; overflow: hidden; padding: 0; + border-top: 1px solid var(--border-color); + padding-top: 12px; .widget-header { - padding: 16px 16px 12px 16px; - margin-bottom: 0; + padding: 0; + margin-bottom: 8px; } .contact-search-bar { - padding: 0 16px 12px 16px; + padding: 0 0 10px; position: relative; border-bottom: 1px solid var(--border-color); @@ -1233,22 +1252,23 @@ width: 100%; background: var(--bg-tertiary); border: 1px solid transparent; - border-radius: 8px; - padding: 8px 32px 8px 12px; + border-radius: var(--sns-border-radius-sm); + height: 32px; + padding: 0 32px 0 10px; font-size: 12px; color: var(--text-primary); &:focus { outline: none; - background: var(--bg-primary); - border-color: var(--primary); + background: var(--bg-secondary); + border-color: transparent; } } .search-icon { position: absolute; - right: 28px; - top: 8px; + right: 10px; + top: 9px; color: var(--text-tertiary); pointer-events: none; display: none; @@ -1256,8 +1276,8 @@ .clear-icon { position: absolute; - right: 28px; - top: 8px; + right: 10px; + top: 9px; color: var(--text-tertiary); cursor: pointer; display: flex; @@ -1270,7 +1290,7 @@ align-items: center; justify-content: space-between; gap: 10px; - padding: 10px 16px; + padding: 8px 0; border-bottom: 1px dashed color-mix(in srgb, var(--border-color) 72%, transparent); } @@ -1287,9 +1307,9 @@ background: color-mix(in srgb, var(--bg-primary) 84%, rgba(var(--primary-rgb), 0.08)); color: var(--text-secondary); border-radius: 999px; - padding: 5px 10px; + padding: 4px 9px; font-size: 12px; - font-weight: 600; + font-weight: 500; line-height: 1.2; cursor: pointer; transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease; @@ -1312,7 +1332,7 @@ } .contact-count-progress { - padding: 8px 16px 10px; + padding: 7px 0 8px; font-size: 11px; color: var(--text-tertiary); border-bottom: 1px dashed color-mix(in srgb, var(--border-color) 70%, transparent); @@ -1320,7 +1340,7 @@ } .contact-interaction-hint { - padding: 10px 16px 0; + padding: 8px 0 0; font-size: 11px; line-height: 1.5; color: var(--text-tertiary); @@ -1328,23 +1348,20 @@ .contact-list-scroll { flex: 1; - overflow-y: auto; - padding: 8px 12px; - display: flex; - flex-direction: column; - gap: 0; + min-height: 0; + padding: 8px 0 0; + + .contact-list-virtuoso { + height: 100%; + } .contact-row { display: flex; align-items: center; - gap: 8px; - margin-bottom: 4px; - border-radius: var(--sns-border-radius-md); - transition: transform 0.2s ease; - - &:hover { - transform: translateX(2px); - } + gap: 4px; + min-height: 40px; + border-radius: var(--sns-border-radius-sm); + transition: background 0.15s ease; &.is-selected .contact-main-btn { background: rgba(var(--primary-rgb), 0.06); @@ -1362,7 +1379,7 @@ } .contact-select-btn { - width: 32px; + width: 28px; height: 32px; flex-shrink: 0; border: none; @@ -1373,7 +1390,7 @@ align-items: center; justify-content: center; cursor: pointer; - transition: background 0.2s ease, color 0.2s ease; + transition: background 0.15s ease, color 0.15s ease; &:hover { background: rgba(var(--primary-rgb), 0.1); @@ -1390,17 +1407,17 @@ min-width: 0; display: flex; align-items: center; - gap: 12px; - padding: 10px 12px; - border-radius: var(--sns-border-radius-md); + gap: 8px; + padding: 5px 6px; + border-radius: var(--sns-border-radius-sm); border: 1px solid transparent; background: transparent; cursor: pointer; - transition: background 0.2s ease, border-color 0.2s ease; + transition: background 0.15s ease, border-color 0.15s ease; text-align: left; &:hover { - background: var(--hover-bg); + background: var(--bg-hover); } } @@ -1412,7 +1429,7 @@ gap: 2px; .contact-name { - font-size: 14px; + font-size: 13px; color: var(--text-secondary); white-space: nowrap; overflow: hidden; @@ -1422,7 +1439,7 @@ .contact-post-count-wrap { margin-left: 8px; - min-width: 46px; + min-width: 42px; display: flex; justify-content: flex-end; align-items: center; @@ -1430,7 +1447,7 @@ } .contact-post-count { - font-size: 12px; + font-size: 11px; color: var(--text-tertiary); font-variant-numeric: tabular-nums; white-space: nowrap; @@ -1453,9 +1470,9 @@ display: flex; align-items: center; gap: 8px; - padding: 12px 16px 14px; + padding: 10px 0 0; border-top: 1px solid var(--border-color); - background: color-mix(in srgb, var(--bg-primary) 86%, var(--bg-secondary)); + background: transparent; } .contact-batch-summary { @@ -1470,8 +1487,8 @@ border: 1px solid var(--border-color); background: var(--bg-secondary); color: var(--text-secondary); - border-radius: var(--sns-border-radius-md); - height: 32px; + border-radius: var(--sns-border-radius-sm); + height: 30px; padding: 0 10px; display: inline-flex; align-items: center; @@ -1483,7 +1500,7 @@ &:hover { border-color: var(--text-tertiary); - background: var(--hover-bg); + background: var(--bg-hover); color: var(--text-primary); } @@ -1508,9 +1525,9 @@ ========================================= */ .status-indicator { text-align: center; - padding: 16px; + padding: 10px 8px; color: var(--text-tertiary); - font-size: 13px; + font-size: 12px; display: flex; align-items: center; justify-content: center; @@ -1520,8 +1537,10 @@ background: rgba(var(--primary-rgb), 0.1); color: var(--primary); cursor: pointer; + border: 1px solid color-mix(in srgb, var(--primary) 18%, var(--border-color)); border-radius: var(--sns-border-radius-sm); - margin: 0 24px; + margin: 0 0 8px; + width: 100%; } } @@ -1530,45 +1549,44 @@ display: flex; align-items: center; justify-content: center; - padding: 120px 0; + padding: 72px 0; .loading-pulse { display: flex; flex-direction: column; align-items: center; - gap: 20px; + gap: 12px; .pulse-circle { - width: 48px; - height: 48px; + width: 12px; + height: 12px; border-radius: 50%; background: var(--primary, #576b95); - opacity: 0.25; - animation: pulse-ring 1.4s ease-in-out infinite; + opacity: 0.4; + animation: pulse-ring 1.2s ease-in-out infinite; } span { - font-size: 14px; + font-size: 12px; color: var(--text-tertiary); - letter-spacing: 0.5px; } } } @keyframes pulse-ring { 0% { - transform: scale(0.6); - opacity: 0.15; + transform: scale(0.85); + opacity: 0.2; } 50% { - transform: scale(1.0); - opacity: 0.35; + transform: scale(1); + opacity: 0.45; } 100% { - transform: scale(0.6); - opacity: 0.15; + transform: scale(0.85); + opacity: 0.2; } } @@ -1576,20 +1594,25 @@ display: flex; flex-direction: column; align-items: center; - padding: 60px 0; + padding: 56px 0; color: var(--text-tertiary); .no-results-icon { - margin-bottom: 16px; + margin-bottom: 10px; opacity: 0.5; } + p { + margin: 0; + font-size: 13px; + } + .reset-inline { - margin-top: 16px; + margin-top: 12px; background: none; border: 1px solid var(--border-color); - padding: 6px 16px; - border-radius: 20px; + padding: 6px 12px; + border-radius: 8px; cursor: pointer; color: var(--text-secondary); @@ -2760,7 +2783,7 @@ transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease; &:hover:not(:disabled) { - background: var(--hover-bg); + background: var(--bg-hover); color: var(--text-primary); } @@ -2902,7 +2925,7 @@ color: var(--text-secondary); &:hover { - background: var(--hover-bg); + background: var(--bg-hover); color: var(--text-primary); } diff --git a/src/pages/SnsPage.tsx b/src/pages/SnsPage.tsx index 019f73b..e347005 100644 --- a/src/pages/SnsPage.tsx +++ b/src/pages/SnsPage.tsx @@ -1,5 +1,6 @@ import { useEffect, useLayoutEffect, useState, useRef, useCallback, useMemo } from 'react' import { RefreshCw, Search, X, Download, FolderOpen, FileJson, FileText, Image, CheckCircle, AlertCircle, Calendar, Info, Shield, ShieldOff, Loader2, Pause, Play, Square } from 'lucide-react' +import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso' import './SnsPage.scss' import { SnsPost } from '../types/sns' import { SnsPostItem } from '../components/Sns/SnsPostItem' @@ -229,7 +230,8 @@ export default function SnsPage() { const [cacheMigrationDone, setCacheMigrationDone] = useState(false) const [cacheMigrationError, setCacheMigrationError] = useState(null) - const postsContainerRef = useRef(null) + const postsContainerRef = useRef(null) + const postsVirtuosoRef = useRef(null) const jumpCalendarWrapRef = useRef(null) const [hasNewer, setHasNewer] = useState(false) const [loadingNewer, setLoadingNewer] = useState(false) @@ -1110,6 +1112,7 @@ export default function SnsPage() { setHasNewer(false); } + postsVirtuosoRef.current?.scrollToIndex({ index: 0, align: 'start', behavior: 'auto' }) if (postsContainerRef.current) { postsContainerRef.current.scrollTop = 0 } @@ -1764,23 +1767,61 @@ export default function SnsPage() { loadPosts({ reset: true }) }, [loadPosts, selectedContactUsernamesKey]) - const handleScroll = (e: React.UIEvent) => { - const { scrollTop, clientHeight, scrollHeight } = e.currentTarget - if (scrollHeight - scrollTop - clientHeight < 400 && hasMore && !loading && !loadingNewer) { - loadPosts({ direction: 'older' }) - } - if (scrollTop < 10 && hasNewer && !loading && !loadingNewer) { - loadPosts({ direction: 'newer' }) - } - } + const handlePostsEndReached = useCallback(() => { + if (!hasMore || loading || loadingNewer) return + void loadPosts({ direction: 'older' }) + }, [hasMore, loadPosts, loading, loadingNewer]) - const handleWheel = (e: React.WheelEvent) => { - const container = postsContainerRef.current - if (!container) return - if (e.deltaY < -20 && container.scrollTop <= 0 && hasNewer && !loading && !loadingNewer) { - loadPosts({ direction: 'newer' }) - } - } + const renderPostItem = useCallback((_: number, post: SnsPost) => ( +
+ { + if (isVideo) { + void window.electronAPI.window.openVideoPlayerWindow(src) + } else { + void window.electronAPI.window.openImageViewerWindow(src, liveVideoPath || undefined) + } + }} + onDebug={(p) => setDebugPost(p)} + onDelete={handlePostDelete} + onOpenAuthorPosts={openAuthorTimeline} + /> +
+ ), [handlePostDelete, openAuthorTimeline, triggerInstalled]) + + const snsVirtuosoComponents = useMemo(() => ({ + Header: () => ( + <> + {loadingNewer && ( +
+ + 正在检查更新动态 +
+ )} + + {!loadingNewer && hasNewer && ( + + )} + + ), + Footer: () => ( + <> + {loading && visiblePosts.length > 0 && ( +
+ + 正在加载更多 +
+ )} + + {!hasMore && visiblePosts.length > 0 && ( +
已加载全部动态
+ )} + + ) + }), [hasMore, hasNewer, loadPosts, loading, loadingNewer, visiblePosts.length]) return (
@@ -1940,62 +1981,19 @@ export default function SnsPage() {
)} -
- {loadingNewer && ( -
- - 正在检查更新的动态... -
- )} - - {!loadingNewer && hasNewer && ( -
loadPosts({ direction: 'newer' })}> - 有新动态,点击查看 -
- )} - -
- {visiblePosts.map(post => ( - { - if (isVideo) { - void window.electronAPI.window.openVideoPlayerWindow(src) - } else { - void window.electronAPI.window.openImageViewerWindow(src, liveVideoPath || undefined) - } - }} - onDebug={(p) => setDebugPost(p)} - onDelete={handlePostDelete} - onOpenAuthorPosts={openAuthorTimeline} - /> - ))} -
- +
{loading && visiblePosts.length === 0 && (
- 正在加载朋友圈... + 正在加载动态
)} - {loading && visiblePosts.length > 0 && ( -
- - 正在加载更多... -
- )} - - {!hasMore && visiblePosts.length > 0 && ( -
或许过往已无可溯洄,但好在还有可以与你相遇的明天
- )} - {!loading && visiblePosts.length === 0 && (
-
+

未找到相关动态

{(searchKeyword || jumpTargetDate || selectedContactUsernames.length > 0) && (
)} + + {visiblePosts.length > 0 && ( + post.id} + itemContent={renderPostItem} + components={snsVirtuosoComponents} + endReached={handlePostsEndReached} + scrollerRef={(ref) => { + postsContainerRef.current = ref instanceof HTMLElement ? ref : null + }} + defaultItemHeight={220} + increaseViewportBy={{ top: 260, bottom: 520 }} + overscan={{ main: 900, reverse: 480 }} + /> + )}