增加PageItems组件
This commit is contained in:
parent
4264a64ecf
commit
b5d04a867d
@ -6,12 +6,14 @@ export async function GET(request) {
|
|||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const pagetype = searchParams.get('pagetype');
|
const pagetype = searchParams.get('pagetype');
|
||||||
|
const category = searchParams.get('category');
|
||||||
|
const count = searchParams.get('count');
|
||||||
if (!pagetype) {
|
if (!pagetype) {
|
||||||
return Response.json({ error: '缺少pagetype参数' }, { status: 400 });
|
return Response.json({ error: '缺少pagetype参数' }, { status: 400 });
|
||||||
}
|
}
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_listview_data`,
|
`${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_listview_data`,
|
||||||
{ params: { pagetype } }
|
{ params: { pagetype, category, count } }
|
||||||
);
|
);
|
||||||
const data = response.data.message?.data || [];
|
const data = response.data.message?.data || [];
|
||||||
return Response.json({ data });
|
return Response.json({ data });
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import Faqs from "@/components/homes/home-15/Faqs";
|
|||||||
import Gallery from "@/components/homes/home-15/Gallery";
|
import Gallery from "@/components/homes/home-15/Gallery";
|
||||||
import Features from "@/components/homes/home-15/Features";
|
import Features from "@/components/homes/home-15/Features";
|
||||||
import Hero from "@/components/homes/home-15/Hero";
|
import Hero from "@/components/homes/home-15/Hero";
|
||||||
|
import PageItems from "@/components/homes/home-15/PageItems";
|
||||||
import CategoryItems from "@/components/homes/home-15/CategoryItems";
|
import CategoryItems from "@/components/homes/home-15/CategoryItems";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { getSiteSettings } from "@/utlis/siteSettings";
|
import { getSiteSettings } from "@/utlis/siteSettings";
|
||||||
@ -40,7 +41,8 @@ export default function Page() {
|
|||||||
</section>
|
</section>
|
||||||
<section className="wrapper !bg-[#ffffff] ">
|
<section className="wrapper !bg-[#ffffff] ">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<CategoryItems />
|
<CategoryItems pagetype="Jsite Project" category="案例" />
|
||||||
|
<PageItems page_slug="case" page_size={5} columns={5} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -4,11 +4,12 @@ import { Swiper, SwiperSlide } from "swiper/react";
|
|||||||
import { Navigation, Pagination } from "swiper/modules";
|
import { Navigation, Pagination } from "swiper/modules";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
const PAGE_SIZE = 8; // 每页8条,4列2行
|
const PAGE_SIZE = 8; // 每页8条,4列2行
|
||||||
const PAGE_SLUG = "case"; // 博客slug常量
|
const PAGE_SLUG = "case"; // 博客slug常量
|
||||||
|
|
||||||
export default function CategoryItems() {
|
export default function CategoryItems({ pagetype = "", category = "", count = undefined }) {
|
||||||
const [posts, setPosts] = useState([]);
|
const [posts, setPosts] = useState([]);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [totalPages, setTotalPages] = useState(1);
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
@ -18,15 +19,16 @@ export default function CategoryItems() {
|
|||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/get-page-data", {
|
const params = new URLSearchParams({ pagetype });
|
||||||
method: "POST",
|
if (category) params.append("category", category);
|
||||||
headers: { "Content-Type": "application/json" },
|
if (count !== undefined && count !== null) params.append("count", count);
|
||||||
body: JSON.stringify({ slug_list: [PAGE_SLUG], page: currentPage, page_size: PAGE_SIZE }),
|
params.append("page", currentPage);
|
||||||
});
|
params.append("page_size", PAGE_SIZE);
|
||||||
|
const res = await fetch(`/api/get-listview-data?${params.toString()}`);
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
if (Array.isArray(json.data)) {
|
if (Array.isArray(json.data)) {
|
||||||
setPosts(json.data);
|
setPosts(json.data);
|
||||||
setTotalPages(Math.ceil((json.total || 0) / PAGE_SIZE));
|
setTotalPages(Math.ceil((json.total || json.data.length || 0) / PAGE_SIZE));
|
||||||
} else {
|
} else {
|
||||||
setPosts([]);
|
setPosts([]);
|
||||||
setTotalPages(1);
|
setTotalPages(1);
|
||||||
@ -38,7 +40,7 @@ export default function CategoryItems() {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [currentPage]);
|
}, [currentPage, pagetype, category, count]);
|
||||||
|
|
||||||
// 分页控件
|
// 分页控件
|
||||||
function PaginationComp() {
|
function PaginationComp() {
|
||||||
@ -171,7 +173,7 @@ export default function CategoryItems() {
|
|||||||
<div className="post-header !mb-[.9rem]">
|
<div className="post-header !mb-[.9rem]">
|
||||||
<div className="inline-flex !mb-[.4rem] uppercase !tracking-[0.02rem] text-[0.7rem] font-bold !text-[#aab0bc] relative align-top !pl-[1.4rem] before:content-[''] before:absolute before:inline-block before:translate-y-[-60%] before:w-3 before:h-[0.05rem] before:left-0 before:top-2/4 before:bg-[#3f78e0]">
|
<div className="inline-flex !mb-[.4rem] uppercase !tracking-[0.02rem] text-[0.7rem] font-bold !text-[#aab0bc] relative align-top !pl-[1.4rem] before:content-[''] before:absolute before:inline-block before:translate-y-[-60%] before:w-3 before:h-[0.05rem] before:left-0 before:top-2/4 before:bg-[#3f78e0]">
|
||||||
<a href="#" className="hover" rel="category">
|
<a href="#" className="hover" rel="category">
|
||||||
{post.category || post.categories || ""}
|
{post.category || ""}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="post-title !mt-1 !leading-[1.35] !mb-0">
|
<h2 className="post-title !mt-1 !leading-[1.35] !mb-0">
|
||||||
@ -229,3 +231,12 @@ export default function CategoryItems() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CategoryItems.propTypes = {
|
||||||
|
pagetype: PropTypes.string,
|
||||||
|
category: PropTypes.string,
|
||||||
|
count: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|||||||
240
components/homes/home-15/PageItems.jsx
Normal file
240
components/homes/home-15/PageItems.jsx
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
"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";
|
||||||
|
|
||||||
|
export default function PageItems({ page_slug = "", page_size = 8, columns = 4 }) {
|
||||||
|
const [posts, setPosts] = useState([]);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
// 静态映射,避免 Tailwind JIT 无法识别动态类名
|
||||||
|
const colClass = {
|
||||||
|
1: "lg:grid-cols-1",
|
||||||
|
2: "lg:grid-cols-2",
|
||||||
|
3: "lg:grid-cols-3",
|
||||||
|
4: "lg:grid-cols-4",
|
||||||
|
5: "lg:grid-cols-5",
|
||||||
|
6: "lg:grid-cols-6",
|
||||||
|
}[columns] || "lg:grid-cols-4";
|
||||||
|
|
||||||
|
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 }),
|
||||||
|
});
|
||||||
|
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, page_slug, page_size]);
|
||||||
|
|
||||||
|
// 分页控件
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染卡片内容(只返回图片部分,结构在下方article里)
|
||||||
|
function renderCardImage(post, idx) {
|
||||||
|
// 多图轮播
|
||||||
|
if (Array.isArray(post.images) && post.images.length > 1) {
|
||||||
|
return (
|
||||||
|
<Swiper
|
||||||
|
className="swiper"
|
||||||
|
modules={[Navigation, Pagination]}
|
||||||
|
pagination={{ clickable: true, el: `.spdb${idx}` }}
|
||||||
|
navigation={{ prevEl: `.snbpb${idx}`, nextEl: `.snbnb${idx}` }}
|
||||||
|
>
|
||||||
|
{post.images.map((img, i) => (
|
||||||
|
<SwiperSlide key={i} className="swiper-slide">
|
||||||
|
<Image
|
||||||
|
className="!transition-all !duration-[0.35s] !ease-in-out group-hover:scale-105 w-full h-auto"
|
||||||
|
alt={post.title || "image"}
|
||||||
|
src={img}
|
||||||
|
width={960}
|
||||||
|
height={600}
|
||||||
|
/>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 视频
|
||||||
|
if (post.video_src || post.videoId) {
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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
|
||||||
|
className="!transition-all !duration-[0.35s] !ease-in-out group-hover:scale-105 w-full h-auto"
|
||||||
|
alt={post.title || "image"}
|
||||||
|
src={img}
|
||||||
|
width={960}
|
||||||
|
height={600}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSummary(text, maxLen = 100) {
|
||||||
|
if (!text) return "";
|
||||||
|
// 中文:直接按字符截断
|
||||||
|
if (/[\u4e00-\u9fa5]/.test(text)) {
|
||||||
|
return text.length > maxLen ? text.slice(0, maxLen) + "..." : text;
|
||||||
|
}
|
||||||
|
// 英文:按字符截断,但不截断单词
|
||||||
|
if (text.length <= maxLen) return text;
|
||||||
|
let cut = text.slice(0, maxLen);
|
||||||
|
// 如果最后一个字符不是空格,向前找到最近的空格
|
||||||
|
if (!/\s/.test(text[maxLen])) {
|
||||||
|
const lastSpace = cut.lastIndexOf(" ");
|
||||||
|
if (lastSpace > 0) cut = cut.slice(0, lastSpace);
|
||||||
|
}
|
||||||
|
return cut + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="xl:w-10/12 lg:w-10/12 w-full flex-[0_0_auto] !px-[15px] max-w-full !mx-auto my-[150px]">
|
||||||
|
<div
|
||||||
|
className={`blog classic-view grid grid-cols-1 sm:grid-cols-2 ${colClass} gap-6`}
|
||||||
|
>
|
||||||
|
{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 !mb-8">
|
||||||
|
<div className="card">
|
||||||
|
{/* 图片/轮播/视频部分 */}
|
||||||
|
<figure className="card-img-top overlay overlay-1 hover-scale group">
|
||||||
|
<Link href={post.slug ? `/${page_slug}/${post.slug}` : "#"} className="block relative">
|
||||||
|
{renderCardImage(post, idx)}
|
||||||
|
<span className="bg"></span>
|
||||||
|
<figcaption className="group-hover:opacity-100 absolute w-full h-full opacity-0 text-center px-4 py-3 inset-0 z-[5] pointer-events-none p-2">
|
||||||
|
<h5 className="from-top !mb-0 absolute w-full translate-y-[-80%] p-[.75rem_1rem] left-0 top-2/4">
|
||||||
|
查看详情
|
||||||
|
</h5>
|
||||||
|
</figcaption>
|
||||||
|
</Link>
|
||||||
|
</figure>
|
||||||
|
{/* 文字内容 */}
|
||||||
|
<div className="card-body flex-[1_1_auto] p-[40px] xl:!p-[2rem_2.5rem_1.25rem] lg:!p-[2rem_2.5rem_1.25rem] md:!p-[2rem_2.5rem_1.25rem] max-md:pb-4">
|
||||||
|
<div className="post-header !mb-[.9rem]">
|
||||||
|
<div className="inline-flex !mb-[.4rem] uppercase !tracking-[0.02rem] text-[0.7rem] font-bold !text-[#aab0bc] relative align-top !pl-[1.4rem] before:content-[''] before:absolute before:inline-block before:translate-y-[-60%] before:w-3 before:h-[0.05rem] before:left-0 before:top-2/4 before:bg-[#3f78e0]">
|
||||||
|
<a href={`/${page_slug}`} className="hover" rel="category">
|
||||||
|
{post.category || ""}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h2 className="post-title !mt-1 !leading-[1.35] !mb-0">
|
||||||
|
<Link
|
||||||
|
className="!text-[#343f52] hover:!text-[#3f78e0]"
|
||||||
|
href={post.slug ? `/${page_slug}/${post.slug}` : "#"}
|
||||||
|
>
|
||||||
|
{post.title}
|
||||||
|
</Link>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="!relative">
|
||||||
|
<p>{getSummary(post.subtitle)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 底部信息 */}
|
||||||
|
<div className="card-footer xl:!p-[1.25rem_2.5rem_1.25rem] lg:!p-[1.25rem_2.5rem_1.25rem] md:!p-[1.25rem_2.5rem_1.25rem] p-[18px_40px]">
|
||||||
|
<ul className="!text-[0.7rem] !text-[#aab0bc] m-0 p-0 list-none flex !mb-0">
|
||||||
|
<li className="post-date inline-block">
|
||||||
|
<i className="uil uil-calendar-alt pr-[0.2rem] align-[-.05rem] before:content-['\\e9ba']" />
|
||||||
|
<span>{post.date || post.published_at || ""}</span>
|
||||||
|
</li>
|
||||||
|
{post.author && (
|
||||||
|
<li className="post-author inline-block before:content-[''] before:inline-block before:w-[0.2rem] before:h-[0.2rem] before:opacity-50 before:m-[0_.6rem_0] before:rounded-[100%] before:align-[.15rem] before:bg-[#aab0bc]">
|
||||||
|
<a className="!text-[#aab0bc] hover:!text-[#3f78e0] hover:!border-[#3f78e0]" href="#">
|
||||||
|
<i className="uil uil-user pr-[0.2rem] align-[-.05rem] before:content-['\\ed6f']" />
|
||||||
|
<span>{post.author}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{typeof post.comments === 'number' && (
|
||||||
|
<li className="post-comments inline-block before:content-[''] before:inline-block before:w-[0.2rem] before:h-[0.2rem] before:opacity-50 before:m-[0_.6rem_0] before:rounded-[100%] before:align-[.15rem] before:bg-[#aab0bc]">
|
||||||
|
<a className="!text-[#aab0bc] hover:!text-[#3f78e0] hover:!border-[#3f78e0]" href="#">
|
||||||
|
<i className="uil uil-comment pr-[0.2rem] align-[-.05rem] before:content-['\\ea54']" />
|
||||||
|
{post.comments}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{typeof post.likes === 'number' && (
|
||||||
|
<li className="post-likes !ml-auto inline-block">
|
||||||
|
<a className="!text-[#aab0bc] hover:!text-[#3f78e0] hover:!border-[#3f78e0]" href="#">
|
||||||
|
<i className="uil uil-heart-alt pr-[0.2rem] align-[-.05rem] before:content-['\\eb60']" />
|
||||||
|
{post.likes}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<PaginationComp />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user