重构文章页面,删除不再使用的组件和样式文件,优化布局逻辑,添加微信朋友圈风格的自定义颜色和阴影效果,提升整体用户体验和视觉效果。

This commit is contained in:
宇阳
2026-01-29 20:41:08 +08:00
parent f98ed89421
commit 2823d1fabf
69 changed files with 129 additions and 98 deletions

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 868 B

After

Width:  |  Height:  |  Size: 868 B

12
src/app/(main)/layout.tsx Normal file
View File

@@ -0,0 +1,12 @@
import Header from '@/components/Header';
import Footer from '@/components/Footer';
export default function MainLayout({ children }: { children: React.ReactNode }) {
return (
<>
<Header />
<div className="min-h-[calc(100vh-300px)]">{children}</div>
<Footer />
</>
);
}

View File

@@ -3,12 +3,6 @@ import { Metadata } from 'next';
import HeroUIProvider from '@/components/HeroUIProvider';
import NProgress from '@/components/NProgress';
import Header from '@/components/Header';
import Footer from '@/components/Footer';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import Tools from '@/components/Tools';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import Confetti from '@/components/Confetti';
import RouteChangeHandler from '@/components/RouteChangeHandler';
import { getWebConfigDataAPI } from '@/api/config';
@@ -117,23 +111,11 @@ export default async function RootLayout({ children }: Readonly<{ children: Reac
<body id="root" className={`dark:!bg-black-a`}>
{/* 数据注入 */}
<InjectData />
{/* 🎉 礼花效果 */}
{/* <Confetti /> */}
{/* 进度条组件 */}
<NProgress />
{/* 顶部导航组件 */}
<Header />
{/* 主体内容 */}
<HeroUIProvider>
<div className="min-h-[calc(100vh-300px)]">{children}</div>
</HeroUIProvider>
{/* 底部组件 */}
<Footer />
{/* 右侧工具栏组件 */}
{/* <Tools /> */}
{/* 主体内容:各路由组/页面通过自己的 layout 决定是否包含 Header/Footer */}
<HeroUIProvider>{children}</HeroUIProvider>
{/* 悬浮块 */}
<FloatingBlock />

View File

@@ -8,21 +8,27 @@ interface Props {
}
export default ({ list }: Props) => {
if (!list?.length) return null;
return (
<>
{!!list?.length && (
<div className={`flex justify-center mt-4 w-full sm:w-3/6`}>
<PhotoProvider speed={() => 800} easing={(type) => (type === 2 ? 'cubic-bezier(0.36, 0, 0.66, -0.56)' : 'cubic-bezier(0.34, 1.56, 0.64, 1)')}>
<div className={`grid gap-2 ${list.length === 1 ? 'grid-cols-1 justify-center' : 'grid-cols-2 md:grid-cols-3'}`}>
{list.map((url, index) => (
<PhotoView key={index} src={url}>
<img src={url} alt="闪念图片" className="rounded-2xl w-full h-full object-cover cursor-pointer" />
</PhotoView>
))}
<PhotoProvider speed={() => 800} easing={(type) => (type === 2 ? 'cubic-bezier(0.36, 0, 0.66, -0.56)' : 'cubic-bezier(0.34, 1.56, 0.64, 1)')}>
{list.length === 1 ? (
<div className="max-w-[70%]">
<PhotoView src={list[0]}>
<img src={list[0]} alt="闪念图片" className="w-full h-auto rounded-sm object-cover cursor-pointer active:opacity-90" />
</PhotoView>
</div>
) : (
<div className="grid grid-cols-3 gap-1 max-w-[90%]">
{list.map((url, index) => (
<div key={index} className="aspect-square bg-wx-gray dark:bg-black-a overflow-hidden cursor-pointer active:opacity-90">
<PhotoView src={url}>
<img src={url} alt="闪念图片" className="w-full h-full object-cover" />
</PhotoView>
</div>
</PhotoProvider>
))}
</div>
)}
</>
</PhotoProvider>
);
};

View File

@@ -15,30 +15,33 @@ export default function RecordCard({ id, content, images, createTime, user }: Re
const imageList: string[] = Array.isArray(images) ? images : JSON.parse((images as string) || '[]');
return (
<div key={id} className="flex flex-col sm:flex-row">
<img src={user?.avatar} alt="作者头像" width={56} height={56} className="hidden sm:block rounded-lg border dark:border-black-b h-14 mr-2" />
<div className="flex sm:hidden">
<img src={user?.avatar} alt="作者头像" width={44} height={44} className="rounded-lg border dark:border-black-b h-11 mr-2" />
<div className="flex sm:hidden items-center my-1.5 ml-2 space-x-4">
<h3>{user?.name}</h3>
<span className="text-xs">{dayFormat(createTime as any)}</span>
</div>
<article key={id} className="flex space-x-3">
<div className="flex-shrink-0">
<img
src={user?.avatar}
alt="作者头像"
width={40}
height={40}
className="w-10 h-10 rounded-lg object-cover cursor-pointer active:bg-wx-gray"
/>
</div>
<div className="mt-2 sm:mt-0 w-full">
<div className="hidden sm:flex items-center my-1.5 ml-4 space-x-4">
<h3>{user?.name}</h3>
<span className="text-xs">{dayFormat(createTime as any)}</span>
<div className="flex-grow min-w-0">
<h3 className="text-wx-blue font-semibold text-[15px] cursor-pointer w-fit mb-1">{user?.name}</h3>
<div className="text-[15px] leading-6 mb-2 text-wx-text break-words">
<Editor value={content} />
</div>
<div className="w-full p-4 border dark:border-black-b rounded-3xl rounded-tl-none bg-[rgba(255,255,255,0.7)] dark:bg-[rgba(30,36,46,0.9)] backdrop-blur-sm">
<Editor value={content} />
<div className="mb-3">
<ImageList list={imageList} />
</div>
<div className="flex justify-between items-center text-xs text-wx-light">
<span>{dayFormat(createTime as number)}</span>
<div className="bg-wx-gray dark:bg-black-a px-2 py-1 rounded text-wx-blue font-bold cursor-pointer active:opacity-80">
<span className="tracking-widest text-lg leading-3">··</span>
</div>
</div>
</div>
</div>
</article>
);
}

View File

@@ -22,7 +22,6 @@ export default () => {
const [initialLoading, setInitialLoading] = useState(true);
const currentPageRef = useRef(1);
// 获取记录列表
const fetchRecordList = useCallback(async (page: number, append: boolean = false) => {
setLoading(true);
try {
@@ -51,11 +50,9 @@ export default () => {
}
}, []);
// 初始加载:获取用户信息、主题配置和第一页记录
useEffect(() => {
const fetchInitialData = async () => {
try {
// 并行获取用户信息、主题配置和第一页记录
const [userResponse, themeResponse] = await Promise.all([getAuthorDataAPI(), getWebConfigDataAPI<{ value: Theme }>('theme')]);
if (userResponse?.data) {
@@ -65,7 +62,6 @@ export default () => {
setTheme(themeResponse.data.value);
}
// 获取第一页记录
setRecords([]);
setHasMore(true);
setInitialLoading(true);
@@ -79,13 +75,10 @@ export default () => {
fetchInitialData();
}, [fetchRecordList]);
// 滚动监听
useEffect(() => {
const handleScroll = () => {
// 如果正在加载或没有更多数据,则不处理
if (loading || !hasMore) return;
// 检查是否滚动到底部距离底部100px时触发
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
@@ -98,7 +91,6 @@ export default () => {
}
};
// 使用防抖优化滚动事件
let timeoutId: NodeJS.Timeout;
const debouncedHandleScroll = () => {
clearTimeout(timeoutId);
@@ -112,53 +104,79 @@ export default () => {
};
}, [hasMore, loading, totalPages, fetchRecordList]);
const coverImage = (theme as { record_cover?: string })?.record_cover || theme?.covers?.split?.(',')?.[0] || 'https://images.unsplash.com/photo-1470770841072-f978cf4d019e?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80';
return (
<>
<title>🏕 </title>
<meta name="description" content="🏕️ 闪念" />
<div className="bg-[linear-gradient(to_right,#fff1eb_0%,#d0edfb_100%)] dark:bg-[linear-gradient(to_right,#232931_0%,#232931_100%)]">
<div className="w-full lg:w-[800px] px-6 lg:px-0 mx-auto pt-24 pb-10">
<div className="flex items-center flex-col p-4 mb-10 border dark:border-black-b rounded-lg bg-white dark:bg-black-b bg-[url('https://bu.dusays.com/2025/12/04/6930fe4e06985.jpg')] bg-no-repeat bg-center bg-cover ">
<img src={user?.avatar} alt="作者头像" width={80} height={80} className="w-20 h-20 rounded-full avatar-animation shadow-[5px_11px_30px_20px_rgba(255,255,255,0.3)]" />
<h2 className="my-2 text-white">{theme?.record_name}</h2>
<h4 className="text-xs text-gray-300">{theme?.record_info}</h4>
</div>
{initialLoading ? (
<div className="flex justify-center items-center py-20">
<Loading />
<div className="bg-gray-100 min-h-screen flex justify-center text-wx-text selection:bg-wx-blue selection:text-white dark:bg-black-a pt-24">
<main className="w-full max-w-[430px] bg-white min-h-screen relative shadow-2xl flex flex-col overflow-y-auto dark:bg-black-b">
{/* 封面图区域 (Hero) */}
<section className="relative mb-16">
<div
className="h-80 w-full bg-cover bg-center cursor-pointer"
style={{ backgroundImage: `url('${coverImage}')` }}
/>
<div className="absolute -bottom-10 right-4 flex items-end space-x-3">
<div className="text-white font-bold text-lg mb-4 drop-shadow-md select-none">
{theme?.record_name || '闪念'} - {user?.name || ''}
</div>
<div className="w-20 h-20 rounded-xl overflow-hidden border-2 border-white shadow-sm cursor-pointer active:opacity-80 transition-opacity">
<img src={user?.avatar} alt="头像" className="w-full h-full object-cover" width={80} height={80} />
</div>
</div>
) : (
<>
<div className="space-y-12">
{!!records?.length && records.map((item) => <RecordCard key={item.id} id={item.id as any} content={item.content as any} images={item.images as any} createTime={item.createTime as any} user={user as any} />)}
</section>
{/* 内容列表 */}
<div className="px-4 pb-10 space-y-8">
{initialLoading ? (
<div className="flex justify-center items-center py-20">
<Loading />
</div>
) : (
<>
{!!records?.length &&
records.map((item, index) => (
<div key={item.id}>
<RecordCard
id={item.id as never}
content={item.content as string}
images={item.images as string[]}
createTime={item.createTime as number}
user={user as User}
/>
{index < records.length - 1 && <div className="border-b border-gray-100 dark:border-wx-border mt-8" />}
</div>
))}
<Show is={!records?.length}>
<Empty info="内容为空~" />
</Show>
</div>
{/* 懒加载指示器 */}
{loading && records.length > 0 && (
<div className="flex justify-center items-center py-8 mt-5 gap-2">
<div className="flex items-center gap-2 text-gray-500 dark:text-gray-400 text-sm">
<svg className="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>...</span>
{loading && records.length > 0 && (
<div className="flex justify-center items-center py-8 gap-2">
<div className="flex items-center gap-2 text-wx-light text-sm">
<svg className="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
<span>...</span>
</div>
</div>
</div>
)}
{!hasMore && records.length > 0 && (
<div className="flex justify-center items-center py-8 mt-5">
<div className="text-gray-500 dark:text-gray-400 text-sm"></div>
</div>
)}
</>
)}
</div>
)}
{!hasMore && records.length > 0 && (
<div className="text-center py-6">
<div className="h-px bg-gray-200 dark:bg-wx-border w-full mb-3" />
<span className="text-xs text-wx-light"></span>
</div>
)}
</>
)}
</div>
</main>
</div>
</>
);

View File

@@ -17,7 +17,7 @@ import { getCateListAPI } from '@/api/cate';
import { useConfigStore } from '@/stores';
const Header = () => {
export default () => {
const patchName = usePathname();
const { isDark, setIsDark, theme } = useConfigStore();
@@ -153,5 +153,3 @@ const Header = () => {
</>
);
};
export default Header;

View File

@@ -16,7 +16,19 @@ const config: Config = {
colors: {
primary: '#539dfd', // 添加自定义颜色
'black-a': '#232931',
'black-b': '#2c333e'
'black-b': '#2c333e',
// 微信朋友圈风格
wx: {
bg: '#ededed',
blue: '#576b95',
text: '#111111',
gray: '#f7f7f7',
light: '#b2b2b2',
border: '#e5e5e5',
},
},
boxShadow: {
'wx-menu': '0 0 8px rgba(0, 0, 0, 0.15)',
},
transitionDuration: {
'DEFAULT': '300ms', // 添加默认过渡时间为0.3秒