修改ImageViewer显示错误的bug,添加到底了提示,暂时去除下拉刷新功能
This commit is contained in:
72
src/App.tsx
72
src/App.tsx
@@ -1,7 +1,7 @@
|
|||||||
// 你好,感谢你愿意看源代码,但是悄悄告诉你,代码其实是AI写的所以质量很差喵。抱歉呜呜呜😭。
|
// 你好,感谢你愿意看源代码,但是悄悄告诉你,代码其实是AI写的所以质量很差喵。抱歉呜呜呜😭。
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { FluentProvider, webLightTheme, webDarkTheme } from '@fluentui/react-components';
|
import { FluentProvider, webLightTheme, webDarkTheme, tokens } from '@fluentui/react-components';
|
||||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
import PostCard from './components/PostCard';
|
import PostCard from './components/PostCard';
|
||||||
import MainLayout from './layouts/MainLayout';
|
import MainLayout from './layouts/MainLayout';
|
||||||
@@ -16,6 +16,7 @@ import ReportState from './components/ReportState';
|
|||||||
import AdminPage from './components/AdminPage';
|
import AdminPage from './components/AdminPage';
|
||||||
import InitPage from './pages/InitPage';
|
import InitPage from './pages/InitPage';
|
||||||
import NotFound from './pages/NotFound';
|
import NotFound from './pages/NotFound';
|
||||||
|
import ImageViewer from './components/ImageViewer';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isDarkMode, setIsDarkMode] = React.useState(false);
|
const [isDarkMode, setIsDarkMode] = React.useState(false);
|
||||||
@@ -32,15 +33,15 @@ function App() {
|
|||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
const observer = useRef<IntersectionObserver>(null);
|
const observer = useRef<IntersectionObserver>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const touchStartYRef = useRef<number | null>(null);
|
|
||||||
const pullDeltaRef = useRef<number>(0);
|
|
||||||
const lastRefreshAtRef = useRef<number>(0);
|
const lastRefreshAtRef = useRef<number>(0);
|
||||||
const [pullOffset, setPullOffset] = useState(0);
|
|
||||||
const [offsetAnimated, setOffsetAnimated] = useState(false);
|
|
||||||
const MAX_PULL = 80; // 最大下拉位移
|
|
||||||
const TRIGGER_PULL = 60; // 触发刷新的阈值
|
|
||||||
const DAMPING = 0.5; // 阻尼系数,减少位移幅度
|
|
||||||
const REFRESH_COOLDOWN_MS = 5000; // 刷新冷却时间
|
const REFRESH_COOLDOWN_MS = 5000; // 刷新冷却时间
|
||||||
|
const [imageViewer, setImageViewer] = useState<{ open: boolean; src?: string; alt?: string }>({ open: false });
|
||||||
|
|
||||||
|
const openImageViewer = (src?: string, alt?: string) => {
|
||||||
|
if (!src) return;
|
||||||
|
setImageViewer({ open: true, src, alt });
|
||||||
|
};
|
||||||
|
const closeImageViewer = () => setImageViewer({ open: false });
|
||||||
|
|
||||||
const lastArticleRef = useCallback((node: HTMLDivElement) => {
|
const lastArticleRef = useCallback((node: HTMLDivElement) => {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
@@ -66,40 +67,9 @@ function App() {
|
|||||||
if (containerRef.current) containerRef.current.scrollTop = 0;
|
if (containerRef.current) containerRef.current.scrollTop = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTouchStart: React.TouchEventHandler<HTMLDivElement> = (e) => {
|
// 移除触摸下拉刷新逻辑
|
||||||
touchStartYRef.current = e.touches[0]?.clientY ?? null;
|
|
||||||
pullDeltaRef.current = 0;
|
|
||||||
setOffsetAnimated(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onTouchMove: React.TouchEventHandler<HTMLDivElement> = (e) => {
|
// 撤销 Pointer 事件回退,恢复为纯 Touch 逻辑
|
||||||
const startY = touchStartYRef.current;
|
|
||||||
if (startY == null) return;
|
|
||||||
const currentY = e.touches[0]?.clientY ?? startY;
|
|
||||||
const rawDelta = currentY - startY;
|
|
||||||
pullDeltaRef.current = rawDelta;
|
|
||||||
const atTop = (containerRef.current?.scrollTop ?? 0) <= 0;
|
|
||||||
if (atTop && rawDelta > 0 && !loading && !refreshing) {
|
|
||||||
const offset = Math.min(MAX_PULL, rawDelta * DAMPING);
|
|
||||||
setPullOffset(offset);
|
|
||||||
} else {
|
|
||||||
setPullOffset(0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onTouchEnd: React.TouchEventHandler<HTMLDivElement> = () => {
|
|
||||||
const atTop = (containerRef.current?.scrollTop ?? 0) <= 0;
|
|
||||||
const shouldRefresh = atTop && pullOffset >= TRIGGER_PULL;
|
|
||||||
setOffsetAnimated(true);
|
|
||||||
setPullOffset(0);
|
|
||||||
if (shouldRefresh) {
|
|
||||||
doRefresh();
|
|
||||||
}
|
|
||||||
touchStartYRef.current = null;
|
|
||||||
pullDeltaRef.current = 0;
|
|
||||||
// 结束后移除动画标记,下一次拖动为无动画的跟随效果
|
|
||||||
setTimeout(() => setOffsetAnimated(false), 220);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onWheel: React.WheelEventHandler<HTMLDivElement> = (e) => {
|
const onWheel: React.WheelEventHandler<HTMLDivElement> = (e) => {
|
||||||
const atTop = (containerRef.current?.scrollTop ?? 0) <= 0;
|
const atTop = (containerRef.current?.scrollTop ?? 0) <= 0;
|
||||||
@@ -152,9 +122,6 @@ function App() {
|
|||||||
<div
|
<div
|
||||||
style={{ width: '100%', height: 'calc(100vh - 64px)', overflowY: 'auto', padding: '20px' }}
|
style={{ width: '100%', height: 'calc(100vh - 64px)', overflowY: 'auto', padding: '20px' }}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
onTouchStart={onTouchStart}
|
|
||||||
onTouchMove={onTouchMove}
|
|
||||||
onTouchEnd={onTouchEnd}
|
|
||||||
onWheel={onWheel}
|
onWheel={onWheel}
|
||||||
>
|
>
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -162,8 +129,7 @@ function App() {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minHeight: '100%',
|
minHeight: '100%',
|
||||||
transform: `translateY(${pullOffset}px)`,
|
// 移除下拉位移动画
|
||||||
transition: offsetAnimated ? 'transform 200ms ease' : 'none'
|
|
||||||
}}>
|
}}>
|
||||||
{/* 刷新提示改为 toast,不显示顶部灰字 */}
|
{/* 刷新提示改为 toast,不显示顶部灰字 */}
|
||||||
{articles.map((article, index) => {
|
{articles.map((article, index) => {
|
||||||
@@ -175,6 +141,7 @@ function App() {
|
|||||||
content={article.content}
|
content={article.content}
|
||||||
upvotes={article.upvotes}
|
upvotes={article.upvotes}
|
||||||
downvotes={article.downvotes}
|
downvotes={article.downvotes}
|
||||||
|
onPreviewImage={openImageViewer}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -186,11 +153,21 @@ function App() {
|
|||||||
content={article.content}
|
content={article.content}
|
||||||
upvotes={article.upvotes}
|
upvotes={article.upvotes}
|
||||||
downvotes={article.downvotes}
|
downvotes={article.downvotes}
|
||||||
|
onPreviewImage={openImageViewer}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
{loading && <div>加载中...</div>}
|
{loading && <div>加载中...</div>}
|
||||||
|
{!loading && !hasMore && (
|
||||||
|
<div style={{ width: '100%', display: 'flex', alignItems: 'center', margin: '16px 0' }}>
|
||||||
|
<div style={{ flex: 1, height: 1, backgroundColor: tokens.colorNeutralStroke2 }} />
|
||||||
|
<div style={{ padding: '0 12px', color: tokens.colorNeutralForeground3, textAlign: 'center', whiteSpace: 'nowrap' }}>
|
||||||
|
已经到底了喵~
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1, height: 1, backgroundColor: tokens.colorNeutralStroke2 }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -206,6 +183,9 @@ function App() {
|
|||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
|
{imageViewer.open && imageViewer.src && (
|
||||||
|
<ImageViewer src={imageViewer.src!} alt={imageViewer.alt} onClose={closeImageViewer} />
|
||||||
|
)}
|
||||||
</FluentProvider>
|
</FluentProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
} from '@fluentui/react-icons';
|
} from '@fluentui/react-icons';
|
||||||
import ReportPost from './ReportPost';
|
import ReportPost from './ReportPost';
|
||||||
import CommentSection from './CommentSection';
|
import CommentSection from './CommentSection';
|
||||||
import ImageViewer from './ImageViewer';
|
// 由上层统一挂载 ImageViewer,PostCard 只负责触发
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
card: {
|
card: {
|
||||||
@@ -136,13 +136,15 @@ interface PostCardProps {
|
|||||||
content: string;
|
content: string;
|
||||||
upvotes: number;
|
upvotes: number;
|
||||||
downvotes: number;
|
downvotes: number;
|
||||||
|
onPreviewImage?: (src: string, alt?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostCard = ({
|
const PostCard = ({
|
||||||
id,
|
id,
|
||||||
content,
|
content,
|
||||||
upvotes,
|
upvotes,
|
||||||
downvotes
|
downvotes,
|
||||||
|
onPreviewImage,
|
||||||
}: PostCardProps) => {
|
}: PostCardProps) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const markdownContent = content;
|
const markdownContent = content;
|
||||||
@@ -154,13 +156,7 @@ const PostCard = ({
|
|||||||
const [hasVoted, setHasVoted] = React.useState(false);
|
const [hasVoted, setHasVoted] = React.useState(false);
|
||||||
const [showReportModal, setShowReportModal] = React.useState(false);
|
const [showReportModal, setShowReportModal] = React.useState(false);
|
||||||
const [showComments, setShowComments] = React.useState(false);
|
const [showComments, setShowComments] = React.useState(false);
|
||||||
const [imageViewer, setImageViewer] = React.useState<{ open: boolean; src?: string; alt?: string }>({ open: false });
|
// 图片预览交由上层统一处理,此处仅触发回调
|
||||||
|
|
||||||
const openImageViewer = (src?: string, alt?: string) => {
|
|
||||||
if (!src) return;
|
|
||||||
setImageViewer({ open: true, src, alt });
|
|
||||||
};
|
|
||||||
const closeImageViewer = () => setImageViewer({ open: false });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={styles.card}>
|
<Card className={styles.card}>
|
||||||
@@ -173,7 +169,7 @@ const PostCard = ({
|
|||||||
<img
|
<img
|
||||||
{...props}
|
{...props}
|
||||||
style={{ cursor: 'zoom-in', maxWidth: '100%', height: 'auto', display: 'block' }}
|
style={{ cursor: 'zoom-in', maxWidth: '100%', height: 'auto', display: 'block' }}
|
||||||
onClick={() => openImageViewer(props.src as string, props.alt as string)}
|
onClick={() => onPreviewImage?.(props.src as string, props.alt as string)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
@@ -249,11 +245,9 @@ const PostCard = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{imageViewer.open && imageViewer.src && (
|
{/* 预览层由上层组件统一挂载,这里不再渲染 ImageViewer */}
|
||||||
<ImageViewer src={imageViewer.src!} alt={imageViewer.alt} onClose={closeImageViewer} />
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PostCard;
|
export default PostCard;
|
||||||
|
|||||||
Reference in New Issue
Block a user