mirror of
https://github.com/LiuYuYang01/ThriveX-Blog.git
synced 2026-09-03 06:24:24 +08:00
refactor(ArticleLayout):用 ArticleMeta 组件替换内联文章元数据
- 更新了 Card 和 Classics 组件,以利用新的 ArticleMeta 组件显示文章元数据,提高代码可读性和可维护性。 - 删除了冗余的内联元数据渲染逻辑,简化了组件结构。
This commit is contained in:
@@ -2,11 +2,7 @@ import Link from 'next/link';
|
||||
import { getRandomImage } from '@/utils';
|
||||
import { getThemeCovers } from '@/lib/theme';
|
||||
import { Article } from '@/types/app/article';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { RiFireLine } from 'react-icons/ri';
|
||||
import { IoTimeOutline } from 'react-icons/io5';
|
||||
import { GoTag } from 'react-icons/go';
|
||||
import ArticleMeta from '@/components/ArticleLayout/components/ArticleMeta';
|
||||
import Empty from '@/components/Empty';
|
||||
import Show from '@/components/Show';
|
||||
|
||||
@@ -36,28 +32,7 @@ const Card = async ({ data }: CardProps) => {
|
||||
<h3 className="overflow-hidden relative w-full my-2.5 text_shadow text-white hover:text-primary text-center text-lg md:text-xl lg:text-[22px] xl:text-2xl line-clamp-1">{item.title}</h3>
|
||||
<p className="text-center text-[#cecece] text-sm sm:text-[15px] leading-7 sm:indent-8 line-clamp-2 xl:line-clamp-3">{genArticleInfo(item)}</p>
|
||||
|
||||
<div className={`flex justify-center pt-5 text-end space-x-4 sm:space-x-8`}>
|
||||
<div className="flex items-center text-xs text-white">
|
||||
<span className="pr-1">
|
||||
<IoTimeOutline className="p-1 mt-[-2px] mr-[3px] text-[23px] text-white rounded-full align-middle bg-[#539dfd]" />
|
||||
</span>
|
||||
<span>{dayjs(+item.createTime!).format('YYYY-MM-DD')}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center text-xs text-white">
|
||||
<span className="pr-1">
|
||||
<RiFireLine className="p-1 mt-[-2px] mr-[3px] text-[23px] text-white rounded-full align-middle bg-[#eb373a]" />
|
||||
</span>
|
||||
<span>{item.view}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center text-xs text-white">
|
||||
<span className="pr-1">
|
||||
<GoTag className="p-1 mt-[-2px] mr-[3px] text-[23px] text-white rounded-full align-middle bg-[#f5a630]" />
|
||||
</span>
|
||||
<span>{item.cateList[0]?.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ArticleMeta article={item} className="justify-center sm:justify-start" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,11 +2,7 @@ import Link from 'next/link';
|
||||
import { getRandomImage } from '@/utils';
|
||||
import { getThemeCovers } from '@/lib/theme';
|
||||
import { Article } from '@/types/app/article';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { RiFireLine } from 'react-icons/ri';
|
||||
import { IoTimeOutline } from 'react-icons/io5';
|
||||
import { GoTag } from 'react-icons/go';
|
||||
import ArticleMeta from '@/components/ArticleLayout/components/ArticleMeta';
|
||||
import Empty from '@/components/Empty';
|
||||
import Show from '@/components/Show';
|
||||
|
||||
@@ -49,28 +45,10 @@ const Classics = async ({ data }: ClassicsProps) => {
|
||||
<h3 className="overflow-hidden relative w-full my-2.5 text-white hover:text-primary text-lg md:text-xl lg:text-[22px] xl:text-2xl line-clamp-1">{item.title}</h3>
|
||||
<p className="text-[#cecece] text-sm sm:text-[15px] leading-7 sm:indent-8 line-clamp-2 xl:line-clamp-3">{genArticleInfo(item)}</p>
|
||||
|
||||
<div className={`flex ${index % 2 === 0 ? 'sm:justify-start' : 'sm:justify-end'} justify-center pt-5 text-end space-x-4 sm:space-x-8`}>
|
||||
<div className="flex items-center text-xs text-white">
|
||||
<span className="pr-1">
|
||||
<IoTimeOutline className="p-1 mt-[-2px] mr-[3px] text-[23px] text-white rounded-full align-middle bg-[#539dfd]" />
|
||||
</span>
|
||||
<span>{dayjs(+item.createTime!).format('YYYY-MM-DD')}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center text-xs text-white">
|
||||
<span className="pr-1">
|
||||
<RiFireLine className="p-1 mt-[-2px] mr-[3px] text-[23px] text-white rounded-full align-middle bg-[#eb373a]" />
|
||||
</span>
|
||||
<span>{item.view}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center text-xs text-white">
|
||||
<span className="pr-1">
|
||||
<GoTag className="p-1 mt-[-2px] mr-[3px] text-[23px] text-white rounded-full align-middle bg-[#f5a630]" />
|
||||
</span>
|
||||
<span>{item.cateList[0]?.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ArticleMeta
|
||||
article={item}
|
||||
className={`justify-center ${index % 2 === 0 ? ' sm:justify-end' : 'sm:justify-start'}`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Fragment } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import { FiCalendar, FiEye } from 'react-icons/fi';
|
||||
import { IconType } from 'react-icons';
|
||||
import { Article } from '@/types/app/article';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface ArticleMetaProps {
|
||||
article: Pick<Article, 'createTime' | 'view' | 'cateList'>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface MetaItem {
|
||||
key: string;
|
||||
icon?: IconType;
|
||||
label: string;
|
||||
}
|
||||
|
||||
function buildMetaItems(article: ArticleMetaProps['article']): MetaItem[] {
|
||||
const items: MetaItem[] = [
|
||||
{
|
||||
key: 'date',
|
||||
icon: FiCalendar,
|
||||
label: dayjs(+article.createTime!).format('YYYY-MM-DD'),
|
||||
},
|
||||
{
|
||||
key: 'view',
|
||||
icon: FiEye,
|
||||
label: `${article.view} 阅读`,
|
||||
},
|
||||
];
|
||||
|
||||
const category = article.cateList[0]?.name;
|
||||
if (category) {
|
||||
items.push({ key: 'category', label: category });
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
export default function ArticleMeta({ article, className }: ArticleMetaProps) {
|
||||
const items = buildMetaItems(article);
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-wrap pt-5', className)}>
|
||||
{/* <div className="inline-flex max-w-full flex-wrap items-center rounded-full border border-white/20 bg-black/30 px-1 py-1 shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] backdrop-blur-md"> */}
|
||||
<div className="inline-flex max-w-full flex-wrap items-center rounded-full px-1 py-1">
|
||||
{items.map(({ key, icon: Icon, label }, index) => (
|
||||
<Fragment key={key}>
|
||||
{index > 0 && <span className="mx-0.5 h-3 w-px shrink-0 bg-white/25" aria-hidden />}
|
||||
<span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 text-xs text-white/90 sm:px-3">
|
||||
{Icon && <Icon className="size-3.5 shrink-0 text-white/55" strokeWidth={2} />}
|
||||
<span className="whitespace-nowrap">{label}</span>
|
||||
</span>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -19,10 +19,10 @@ import { getCateNavHref, getCateNavRel, getCateNavTarget } from '@/utils/cateNav
|
||||
import { useConfigStore } from '@/stores';
|
||||
|
||||
const submenuPanelClass =
|
||||
'invisible opacity-0 -translate-y-2 pointer-events-none group-hover/one:visible group-hover/one:opacity-100 group-hover/one:translate-y-0 group-hover/one:pointer-events-auto transition-all duration-300 ease-out absolute left-0 top-full z-10 pt-1 min-w-full w-max max-w-[220px] overflow-hidden rounded-md';
|
||||
'invisible opacity-0 scale-[0.98] pointer-events-none group-hover/one:visible group-hover/one:opacity-100 group-hover/one:scale-100 group-hover/one:pointer-events-auto transition-[opacity,transform,visibility] duration-200 ease-out absolute left-0 top-full z-10 pt-2 min-w-full w-max max-w-[220px] overflow-hidden rounded-md before:absolute before:inset-x-0 before:-top-2 before:h-2 before:content-[""]';
|
||||
|
||||
const submenuItemClass =
|
||||
'group/item relative flex w-full items-center min-w-0 px-5 py-2.5 text-[15px] text-[#666] dark:text-white transition-all duration-300 ease-[cubic-bezier(0.34,1.56,0.64,1)] hover:translate-x-1.5 hover:!text-primary hover:bg-[#f2f2f2] dark:hover:bg-[#323e50] before:absolute before:left-0 before:top-1/2 before:-translate-y-1/2 before:h-0 before:w-[3px] before:rounded-r-full before:bg-primary before:transition-all before:duration-300 hover:before:h-[50%]';
|
||||
'group/item relative flex w-full items-center min-w-0 px-5 py-2.5 text-[15px] text-[#666] dark:text-white transition-colors duration-150 hover:!text-primary hover:bg-[#f2f2f2] dark:hover:bg-[#323e50] before:absolute before:left-0 before:top-1/2 before:-translate-y-1/2 before:h-0 before:w-[3px] before:rounded-r-full before:bg-primary before:transition-[height] before:duration-150 hover:before:h-[50%]';
|
||||
|
||||
export default () => {
|
||||
const patchName = usePathname();
|
||||
@@ -111,18 +111,14 @@ export default () => {
|
||||
<Link href={getCateNavHref(one)} target={getCateNavTarget(one.type)} rel={getCateNavRel(one.type)} className={`flex items-center p-5 text-[15px] whitespace-nowrap group-hover/one:!text-primary ${isPathSty || isScrolled ? 'text-[#333] dark:text-white' : 'text-white'}`}>
|
||||
{one.icon} {one.name}
|
||||
<Show is={!!one.children.length}>
|
||||
<IoIosArrowDown className="ml-2" />
|
||||
<IoIosArrowDown className="ml-2 transition-transform duration-200 group-hover/one:rotate-180" />
|
||||
</Show>
|
||||
</Link>
|
||||
|
||||
<Show is={!!one.children.length}>
|
||||
<ul className={`${submenuPanelClass} backdrop-blur-[5px] bg-[rgba(255,255,255,0.95)] dark:bg-[rgba(44,51,62,0.95)]`} style={{ boxShadow: '0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.08)' }}>
|
||||
{one.children?.map((two, index) => (
|
||||
<li
|
||||
key={two.id}
|
||||
className="opacity-0 -translate-x-2 group-hover/one:opacity-100 group-hover/one:translate-x-0 transition-all duration-300 ease-out"
|
||||
style={{ transitionDelay: `${index * 45 + 60}ms` }}
|
||||
>
|
||||
{one.children?.map((two) => (
|
||||
<li key={two.id}>
|
||||
<Link href={getCateNavHref(two)} target={getCateNavTarget(two.type)} rel={getCateNavRel(two.type)} title={two.name} className={`${submenuItemClass} truncate`}>
|
||||
{two.name}
|
||||
</Link>
|
||||
@@ -139,20 +135,16 @@ export default () => {
|
||||
<Link href={getCateNavHref(one)} target={getCateNavTarget(one.type)} rel={getCateNavRel(one.type)} className={`flex items-center p-5 px-10 text-[15px] whitespace-nowrap group-hover/one:!text-primary ${isPathSty || isScrolled ? 'text-[#333] dark:text-white' : 'text-white'}`}>
|
||||
{one.icon} {one.name}
|
||||
<Show is={!!one.children?.length}>
|
||||
<IoIosArrowDown className="ml-2" />
|
||||
<IoIosArrowDown className="ml-2 transition-transform duration-200 group-hover/one:rotate-180" />
|
||||
</Show>
|
||||
</Link>
|
||||
|
||||
<Show is={!!one.children?.length}>
|
||||
<ul className={`${submenuPanelClass} bg-[rgba(255,255,255,0.95)] dark:bg-[rgba(44,51,62,0.95)]`} style={{ boxShadow: '0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.08)' }}>
|
||||
{one.children?.map((two, index) => (
|
||||
<li
|
||||
key={two.id}
|
||||
className="opacity-0 -translate-x-2 group-hover/one:opacity-100 group-hover/one:translate-x-0 transition-all duration-300 ease-out"
|
||||
style={{ transitionDelay: `${index * 45 + 60}ms` }}
|
||||
>
|
||||
{one.children?.map((two) => (
|
||||
<li key={two.id}>
|
||||
<Link href={getCateNavHref(two)} target={getCateNavTarget(two.type)} rel={getCateNavRel(two.type)} title={two.name} className={`${submenuItemClass} gap-1.5`}>
|
||||
<span className="shrink-0 transition-transform duration-300 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-hover/item:scale-125 group-hover/item:-rotate-6">{two.icon}</span>
|
||||
<span className="shrink-0">{two.icon}</span>
|
||||
<span className="truncate">{two.name}</span>
|
||||
</Link>
|
||||
</li>
|
||||
@@ -169,20 +161,16 @@ export default () => {
|
||||
{one.icon} {one.name}
|
||||
{/* 如果有子分类就显示下拉三角 */}
|
||||
<Show is={!!one.children?.length}>
|
||||
<IoIosArrowDown className="ml-2" />
|
||||
<IoIosArrowDown className="ml-2 transition-transform duration-200 group-hover/one:rotate-180" />
|
||||
</Show>
|
||||
</Link>
|
||||
|
||||
<Show is={!!one.children?.length}>
|
||||
<ul className={`${submenuPanelClass} bg-[rgba(255,255,255,0.95)] dark:bg-[rgba(44,51,62,0.95)]`} style={{ boxShadow: '0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.08)' }}>
|
||||
{one.children?.map((two, index) => (
|
||||
<li
|
||||
key={two.id}
|
||||
className="opacity-0 -translate-x-2 group-hover/one:opacity-100 group-hover/one:translate-x-0 transition-all duration-300 ease-out"
|
||||
style={{ transitionDelay: `${index * 45 + 60}ms` }}
|
||||
>
|
||||
{one.children?.map((two) => (
|
||||
<li key={two.id}>
|
||||
<Link href={getCateNavHref(two)} target={getCateNavTarget(two.type)} rel={getCateNavRel(two.type)} title={two.name} className={`${submenuItemClass} gap-1.5`}>
|
||||
<span className="shrink-0 transition-transform duration-300 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-hover/item:scale-125 group-hover/item:-rotate-6">{two.icon}</span>
|
||||
<span className="shrink-0">{two.icon}</span>
|
||||
<span className="truncate">{two.name}</span>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
Reference in New Issue
Block a user