158 lines
5.8 KiB
JavaScript
158 lines
5.8 KiB
JavaScript
"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>
|
||
);
|
||
}
|