feat: 重设计私信页并优化开源展柜体验

私信改为飞书风布局,支持缩略图表情/图片、按会话草稿与用户搜索发起会话;主页发私信直达消息页。开源展柜修复空列表粘滞并改版目录式 UI。
This commit is contained in:
2026-09-02 02:45:29 +08:00
parent 1c0f7ede55
commit 500e452c2f
11 changed files with 1176 additions and 258 deletions

View File

@@ -9,7 +9,6 @@ import { useAuth } from '../hooks/useAuth';
import { loginPath } from '../utils/authRedirect';
import { formatTime } from '../utils/content';
import { userPath } from '../utils/userPath';
import ComposeMessageDialog from './ComposeMessageDialog';
import UserLink from './UserLink';
interface Props {
@@ -28,7 +27,6 @@ export default function PostAuthorCard({
const { user: me } = useAuth();
const [profile, setProfile] = useState<UserPublic | null>(null);
const [stats, setStats] = useState<UserActivityStats | null>(null);
const [msgOpen, setMsgOpen] = useState(false);
useEffect(() => {
if (!author?.id) {
@@ -78,7 +76,7 @@ export default function PostAuthorCard({
nav(loginPath(profileHref));
return;
}
setMsgOpen(true);
nav(`/messages?peer=${author.id}`);
};
return (
@@ -159,16 +157,6 @@ export default function PostAuthorCard({
</Button>
</div>
</div>
{!isSelf && (
<ComposeMessageDialog
open={msgOpen}
onOpenChange={setMsgOpen}
toUserId={author.id}
toNickname={nick}
onSent={() => nav(`/messages?peer=${author.id}`)}
/>
)}
</div>
);
}

View File

@@ -31,7 +31,7 @@ function pickScrollEl(): HTMLElement | null {
const page = document.querySelector<HTMLElement>('.page-wrap:not(.page-wrap--feed)');
if (page) return page;
const showcase = document.querySelector<HTMLElement>('.showcase-page');
const showcase = document.querySelector<HTMLElement>('.showcase-panel');
if (showcase) return showcase.closest<HTMLElement>('.main-content') ?? showcase;
const admin = document.querySelector<HTMLElement>('.admin-main');

View File

@@ -19,12 +19,14 @@ export default function ShowcaseAsideWidget() {
useEffect(() => {
let cancelled = false;
const hit = getSessionSnapshot<CommunityShowcaseItem[]>(SHOWCASE_KEY);
if (hit !== undefined) {
// 非空快照直接用;空数组可能是启动竞态假空,仍再拉
if (hit !== undefined && hit.length > 0) {
setItems(hit);
setLoading(false);
return;
}
setLoading(true);
if (hit !== undefined) setItems(hit);
setLoading(hit === undefined);
api.communityShowcase()
.then((r) => {
if (cancelled) return;
@@ -33,10 +35,8 @@ export default function ShowcaseAsideWidget() {
setItems(next);
})
.catch(() => {
if (!cancelled) {
setSessionSnapshot(SHOWCASE_KEY, []);
setItems([]);
}
// 失败不写空快照,避免假空粘死
if (!cancelled && hit === undefined) setItems([]);
})
.finally(() => {
if (!cancelled) setLoading(false);
@@ -54,17 +54,13 @@ export default function ShowcaseAsideWidget() {
setItems(next);
})
.catch(() => {
if (opts.showLoading) {
setSessionSnapshot(SHOWCASE_KEY, []);
setItems([]);
}
// 软刷新失败:保持旧 UI
// 软刷新 / 强刷失败:保持旧 UI,不写空快照
})
.finally(() => setLoading(false));
};
const applyHitOrReload = (showLoading: boolean) => {
const hit = getSessionSnapshot<CommunityShowcaseItem[]>(SHOWCASE_KEY);
if (hit !== undefined) {
if (hit !== undefined && hit.length > 0) {
setItems(hit);
setLoading(false);
return;