mirror of
https://github.com/LiuYuYang01/ThriveX-Blog.git
synced 2026-05-06 22:03:08 +08:00
style(components): 统一侧边栏组件样式并优化动态组件交互
调整侧边栏组件的边距和圆角样式,保持一致性 优化动态组件的动画效果和加载状态 重构部分组件代码结构,提升可读性
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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})`,
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user