style(components): 统一侧边栏组件样式并优化动态组件交互

调整侧边栏组件的边距和圆角样式,保持一致性
优化动态组件的动画效果和加载状态
重构部分组件代码结构,提升可读性
This commit is contained in:
宇阳
2026-03-08 17:14:48 +08:00
parent f6e355e859
commit 28cd265b5b
7 changed files with 122 additions and 57 deletions

View File

@@ -1,52 +1,106 @@
'use client';
import { useState, useEffect } from 'react';
import Image from 'next/image';
import { useState, useEffect, useRef } from 'react';
import Link from 'next/link';
import dynamic from '../../svg/dynamic.svg';
import { HiOutlineSpeakerphone } from 'react-icons/hi';
import { FiChevronRight } from 'react-icons/fi';
import { getRecordPagingAPI } from '@/api/record';
import { Record } from '@/types/app/record';
import { extractText } from '@/utils';
export default function Dynamic({ className }: { className?: string }) {
export default function Dynamic({ className = '' }: { className?: string }) {
const [list, setList] = useState<Record[]>([]);
const [currentIndex, setCurrentIndex] = useState(0);
const [isFading, setIsFading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
// 使用 useRef 管理定时器,防止组件卸载时内存泄漏
const timerRef = useRef<NodeJS.Timeout | null>(null);
const fadeTimerRef = useRef<NodeJS.Timeout | null>(null);
const getRecordList = async () => {
const { data } = await getRecordPagingAPI({ pagination: { page: 1, size: 8 } });
setList(data?.result ?? []);
try {
const { data } = await getRecordPagingAPI({ pagination: { page: 1, size: 8 } });
setList(data?.result ?? []);
} catch (error) {
console.error('Failed to fetch records:', error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
getRecordList();
}, []);
// 使用useState来管理当前显示的内容索引
const [currentContentIndex, setCurrentContentIndex] = useState(0);
const [fade, setFade] = useState(true);
// 使用useEffect来设置定时器
useEffect(() => {
const interval = setInterval(() => {
setFade(false); // 开始淡出
setTimeout(() => {
setCurrentContentIndex((prevIndex) => (prevIndex + 1) % list.length);
setFade(true); // 淡入新内容
}, 500); // 500ms的淡出时间
}, 5000); // 每2.5秒切换一次内容包括500ms的过渡时间
if (list.length <= 1) return;
// 清除定时器
return () => clearInterval(interval);
}, [list]);
timerRef.current = setInterval(() => {
setIsFading(true);
fadeTimerRef.current = setTimeout(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % list.length);
setIsFading(false);
}, 400);
}, 4500);
return () => {
if (timerRef.current) clearInterval(timerRef.current);
if (fadeTimerRef.current) clearTimeout(fadeTimerRef.current);
};
}, [list.length]);
if (isLoading) {
return (
<div className={`flex items-center w-full px-4 py-3.5 mb-2 bg-slate-50 dark:bg-[#1a1a1a] rounded-xl border border-transparent ${className}`}>
<div className="h-5 w-24 bg-slate-200 dark:bg-zinc-800 rounded animate-pulse mr-4" />
<div className="h-5 flex-1 bg-slate-200 dark:bg-zinc-800 rounded animate-pulse" />
</div>
);
}
if (list.length === 0) return null;
const currentContent = extractText(list[currentIndex]?.content || '');
return (
<div className={`flex justify-between items-center w-full px-4 py-3 border dark:border-transparent rounded-lg bg-white dark:bg-black-b mb-2 ${className}`}>
<div className="flex items-center">
<Image src={dynamic} alt="动态" width={25} height={25} className="mr-2 w-[25px] h-[25px]" />
<span></span>
<div
className={`
group flex flex-row items-center justify-between w-full px-4 py-3 mb-2
bg-slate-50 dark:bg-black-b
border dark:border-zinc-800/80
rounded-xl hover:shadow-sm
${className}
`}
>
<div className="flex items-center flex-shrink-0 mr-3 lg:mr-5">
<span className="relative flex h-2 w-2 mr-2.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-500"></span>
</span>
<HiOutlineSpeakerphone className="text-blue-500 dark:text-blue-400 text-lg mr-1.5" />
<span className="text-sm font-medium text-slate-700 dark:text-slate-300 whitespace-nowrap"></span>
<div className="hidden sm:block w-px h-3.5 bg-slate-300 dark:bg-zinc-700 mx-3"></div>
</div>
<Link href="/record" className={`flex-1 line-clamp-1 hover:text-primary cursor-pointer ${fade ? 'opacity-100' : 'opacity-0'} transition-opacity`}>
{extractText(list[currentContentIndex]?.content || '')}
<Link href="/record" className="flex-1 flex items-center justify-between overflow-hidden cursor-pointer" title={currentContent}>
<div className="relative flex-1 h-[20px] overflow-hidden">
<span
className={`
absolute left-0 top-0 w-full line-clamp-1 text-sm text-slate-600 dark:text-slate-400
group-hover:text-blue-600 dark:group-hover:text-blue-400
transition-opacity duration-300 ease-in-out
${isFading ? 'opacity-0' : 'opacity-100'}
`}
>
{currentContent}
</span>
</div>
<FiChevronRight className="flex-shrink-0 text-slate-400 group-hover:text-blue-500 ml-2 group-hover:translate-x-0.5 duration-200" />
</Link>
</div>
);

View File

@@ -35,7 +35,7 @@ const Author = async () => {
return (
<div
className="flex flex-col items-center pt-16 bg-no-repeat bg-white dark:bg-black-b w-full h-[350px] mb-5 tw_container"
className="flex flex-col items-center pt-16 bg-no-repeat bg-white dark:bg-black-b w-full h-[350px] mb-3 tw_container"
style={{
backgroundSize: `100% 35%`,
backgroundImage: `url(${avatarBg.src})`,

View File

@@ -8,6 +8,7 @@ import NewCommentSvg from '@/assets/svg/other/comments.svg';
import RandomAvatar from '@/components/RandomAvatar';
import { Comment } from '@/types/app/comment';
import dayjs from 'dayjs';
import { RiTimeLine } from 'react-icons/ri';
const NewComments = () => {
const [list, setList] = useState<Comment[]>([]);
@@ -22,19 +23,25 @@ const NewComments = () => {
}, []);
return (
<div className="flex flex-col tw_container bg-white dark:bg-black-b p-4 mb-5 tw_title">
<div className="flex flex-col tw_container bg-white dark:bg-black-b p-4 mb-3 tw_title">
<div className="tw_title w-full dark:text-white">
<Image src={NewCommentSvg} alt="最新评论" width={33} height={23} />
</div>
<div className="mt-2.5">
{list?.map((item) => (
<Link href={`/article/${item.articleId}`} target="_blank" className="group flex items-center py-2.5 border-b dark:border-b-black-b last:border-b-0" key={item.id}>
{item.avatar ? <img src={item.avatar} className="w-11 h-11 rounded-full mr-2.5 transition-transform hover:scale-110" alt="avatar" /> : <RandomAvatar className="w-11 h-11 rounded-full mr-2.5 transition-transform hover:scale-110" />}
<div className="flex flex-col gap-1 mt-3">
{list.map((item) => (
<Link href={`/article/${item.articleId}`} target="_blank" key={item.id} className="group flex gap-3.5 p-2 -mx-2 rounded-xl hover:bg-slate-50 dark:hover:bg-transparent cursor-pointer">
<div className="relative flex-shrink-0 w-11 h-11 mt-0.5">{item.avatar ? <img src={item.avatar} className="w-full h-full object-cover rounded-full transition-transform duration-300 group-hover:scale-110 ring-2 ring-transparent group-hover:ring-blue-100 dark:group-hover:ring-blue-900/30" alt="avatar" /> : <RandomAvatar className="w-full h-full rounded-full transition-transform duration-300 group-hover:scale-110 ring-2 ring-transparent group-hover:ring-blue-100 dark:group-hover:ring-blue-900/30" />}</div>
<div className="flex flex-col justify-center">
<div className="w-48 text-sm text-gray-600 dark:text-[#8c9ab1] group-hover:text-primary overflow-hidden line-clamp-2">{item.content}</div>
<div className="pt-2.5 text-xs text-gray-400">{dayjs(+item.createTime!).format('YYYY-MM-DD HH:mm')}</div>
<div className="flex flex-col flex-1 min-w-0 justify-center">
<p className="text-[14px] text-slate-600 dark:text-[#8c9ab1] group-hover:text-primary line-clamp-2 leading-relaxed break-words">{item.content}</p>
<div className="flex items-center gap-1 mt-1.5 text-[12px] text-slate-400 dark:text-zinc-500 font-medium">
<RiTimeLine className="text-[14px]" />
<time className="dark:text-gray-500" dateTime={dayjs(+item.createTime!).toISOString()}>
{dayjs(+item.createTime!).format('YYYY-MM-DD HH:mm')}
</time>
</div>
</div>
</Link>
))}

View File

@@ -16,19 +16,23 @@ const RandomArticle = async () => {
return (
<div className="hotArticleComponent">
<div className="flex flex-col tw_container bg-white dark:bg-black-b p-4 mb-5 tw_title">
<div className="flex flex-col tw_container bg-white dark:bg-black-b p-4 mb-3 tw_title">
<div className="tw_title w-full dark:text-white">
<Image src={FireSvg} alt="作者推荐" width={30} height={20} />
<span> </span>
</div>
{/* 文章列表 */}
<div className="w-full">
<div className="flex flex-col px-3 py-2 w-full">
{list?.map((item: Article) => (
<div key={item.id}>
<Link href={`/article/${item.id}`} target="_blank" className="w-full flex items-center py-2 text-gray-600 dark:text-[#8c9ab1] text-sm transition-[padding] hover:!text-primary hover:pl-2">
<IoIosArrowForward className="text-lg mr-1" />
<span className="w-full line-clamp-1">{item.title}</span>
<div key={item.id} className="border-b border-dashed border-gray-100 dark:border-white/10 last:border-none">
<Link
href={`/article/${item.id}`}
target="_blank"
className="group flex items-center justify-between py-3.5 w-full"
>
<span className="text-sm font-medium text-gray-600 dark:text-[#8c9ab1] group-hover:text-primary dark:group-hover:text-primary line-clamp-1 pr-4">{item.title}</span>
<IoIosArrowForward className="text-gray-300 dark:text-gray-600 group-hover:text-primary dark:group-hover:text-primary shrink-0 text-base transform group-hover:translate-x-1 transition-transform duration-200" />
</Link>
</div>
))}

View File

@@ -69,7 +69,7 @@ export default () => {
};
return (
<div className="flex flex-col tw_container bg-white dark:bg-black-b p-4 mb-5 tw_title">
<div className="flex flex-col tw_container bg-white dark:bg-black-b p-4 mb-3 tw_title">
<div className="tw_title w-full dark:text-white">
<Image src={TimerSvg} alt="站点运行时间" width={33} height={23} />
</div>

View File

@@ -5,17 +5,17 @@ import { getPageConfigDataByNameAPI } from '@/api/config';
import { MyData } from '@/types/app/my';
export default async () => {
const { data } = await getPageConfigDataByNameAPI('my');
const { technology_stack } = data?.value as MyData;
return (
<div className="flex flex-col tw_container bg-white dark:bg-black-b p-4 mb-5 tw_title">
<div className="tw_title w-full dark:text-white">
<Image src={StudySvg} alt="最新评论" width={33} height={23} className="mr-2" />
</div>
const { data } = await getPageConfigDataByNameAPI('my');
const { technology_stack } = data?.value as MyData;
return (
<div className="flex flex-col tw_container bg-white dark:bg-black-b p-4 mb-3 tw_title">
<div className="tw_title w-full dark:text-white">
<Image src={StudySvg} alt="最新评论" width={33} height={23} className="mr-2" />
</div>
<div className="mt-4 flex justify-center w-5/6">
<IconCloud iconSlugs={technology_stack ?? []} />
</div>
</div>
);
<div className="mt-4 flex justify-center w-5/6">
<IconCloud iconSlugs={technology_stack ?? []} />
</div>
</div>
);
};

View File

@@ -3,7 +3,7 @@
@tailwind utilities;
.tw_container {
@apply border dark:border-transparent rounded-md transition-shadow ease-in-out hover:shadow-[0_10px_20px_1px_rgb(83,157,253,.1)];
@apply border dark:border-transparent rounded-xl transition-shadow ease-in-out hover:shadow-[0_10px_20px_1px_rgb(83,157,253,.1)];
}
.tw_title {