t10015/components/blogs/Blogs.jsx
2025-08-21 15:58:23 +08:00

158 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useEffect, useState } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Pagination } from "swiper/modules";
import Link from "next/link";
import Image from "next/image";
const PAGE_SIZE = 8; // 每页8条4列2行
const PAGE_SLUG = "case"; // 博客slug常量
export default function Blogs({
parentClass = "xl:w-10/12 lg:w-10/12 w-full flex-[0_0_auto] !px-[15px] max-w-full !mx-auto",
marginTop = true,
}) {
const [posts, setPosts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [loading, setLoading] = useState(false);
useEffect(() => {
async function fetchData() {
setLoading(true);
try {
const res = await fetch("/api/get-page-data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ slug_list: [PAGE_SLUG], page: currentPage, page_size: PAGE_SIZE }),
});
const json = await res.json();
if (Array.isArray(json.data)) {
setPosts(json.data);
setTotalPages(Math.ceil((json.total || 0) / PAGE_SIZE));
} else {
setPosts([]);
setTotalPages(1);
}
} catch (e) {
setPosts([]);
setTotalPages(1);
}
setLoading(false);
}
fetchData();
}, [currentPage]);
// 分页控件
function PaginationComp() {
if (totalPages <= 1) return null;
return (
<nav className="flex justify-center mt-8" aria-label="pagination">
<ul className="pagination flex gap-2">
{Array.from({ length: totalPages }).map((_, idx) => (
<li key={idx}>
<button
className={`px-3 py-1 rounded ${currentPage === idx + 1 ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-700"}`}
onClick={() => setCurrentPage(idx + 1)}
disabled={currentPage === idx + 1}
>
{idx + 1}
</button>
</li>
))}
</ul>
</nav>
);
}
// 渲染卡片内容
function renderCard(post, idx) {
// 多图轮播
if (Array.isArray(post.images) && post.images.length > 1) {
return (
<Swiper
className="w-full h-56 rounded-xl overflow-hidden"
modules={[Navigation, Pagination]}
pagination={{ clickable: true }}
navigation
>
{post.images.map((img, i) => (
<SwiperSlide key={i}>
<Image src={img} alt={post.title || "image"} width={400} height={225} className="w-full h-56 object-cover" />
</SwiperSlide>
))}
</Swiper>
);
}
// 视频
if (post.video_src || post.videoId) {
// 支持YouTube或本地视频
if (post.video_src && (post.video_src.endsWith('.mp4') || post.video_src.startsWith('/files/'))) {
return (
<video controls className="w-full h-56 rounded-xl object-cover">
<source src={post.video_src} type="video/mp4" />
您的浏览器不支持视频播放
</video>
);
}
// YouTube
const vid = post.videoId || (post.video_src && post.video_src.includes('youtube') ? post.video_src.split('embed/')[1] : null);
if (vid) {
return (
<iframe
className="w-full h-56 rounded-xl"
src={`https://www.youtube.com/embed/${vid}`}
title="YouTube video"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
);
}
}
// 单图
const img = post.image || (Array.isArray(post.images) && post.images[0]);
if (img) {
return (
<Image src={img} alt={post.title || "image"} width={400} height={225} className="w-full h-56 object-cover rounded-xl" />
);
}
return null;
}
return (
<div className={parentClass}>
<div className={`blog grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 ${marginTop ? "!mt-[-7rem]" : ""}`}>
{loading ? (
<div className="col-span-4 text-center py-12 text-gray-400">加载中...</div>
) : posts.length === 0 ? (
<div className="col-span-4 text-center py-12 text-gray-400">暂无数据</div>
) : (
posts.map((post, idx) => (
<article key={post.slug || post.id || idx} className="post card bg-white rounded-xl shadow hover:shadow-lg transition overflow-hidden flex flex-col">
<div className="card-img-top relative">
{renderCard(post, idx)}
</div>
<div className="card-body flex-1 p-4 flex flex-col">
<div className="post-header mb-2">
<div className="text-xs text-blue-500 font-bold uppercase mb-1">{post.category || post.categories || ""}</div>
<h2 className="post-title text-lg font-semibold mb-1 line-clamp-2">
<Link href={post.slug ? `/${PAGE_SLUG}/${post.slug}` : "#"} className="hover:text-blue-600">{post.title}</Link>
</h2>
</div>
<div className="text-gray-500 text-sm flex-1 line-clamp-3 mb-2">{post.subtitle}</div>
<div className="flex items-center text-xs text-gray-400 mt-auto">
<span>{post.date || post.published_at || ""}</span>
{post.author && <span className="ml-2">{post.author}</span>}
{typeof post.comments === 'number' && <span className="ml-auto">💬 {post.comments}</span>}
{typeof post.likes === 'number' && <span className="ml-2"> {post.likes}</span>}
</div>
</div>
</article>
))
)}
</div>
<PaginationComp />
</div>
);
}