增加SwiperItems组件
This commit is contained in:
parent
66041bc17c
commit
2b453785da
11
app/page.jsx
11
app/page.jsx
@ -41,8 +41,15 @@ export default function Page() {
|
||||
</section>
|
||||
<section className="wrapper !bg-[#ffffff] ">
|
||||
<div className="w-full">
|
||||
<CategoryItems pagetype="Jsite Project" category="案例" category_slug="case" count={12} columns={4} />
|
||||
<PageItems page_slug="case" page_size={5} columns={5} />
|
||||
<CategoryItems
|
||||
pagetype="Jsite Project"
|
||||
category="案例"
|
||||
category_slug="case"
|
||||
count={8}
|
||||
columns={4}
|
||||
title="应用场景"
|
||||
subtitle="Jingrow 系统行业应用场景及案例"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
|
||||
@ -6,7 +6,7 @@ import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export default function CategoryItems({ pagetype = "", category = "", category_slug = "" , count = 8, columns = 4 }) {
|
||||
export default function CategoryItems({ pagetype = "", category = "", category_slug = "" , count = 8, columns = 4, title = "", subtitle = "" }) {
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@ -133,7 +133,14 @@ export default function CategoryItems({ pagetype = "", category = "", category_s
|
||||
}
|
||||
|
||||
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="xl:w-10/12 lg:w-10/12 w-full flex-[0_0_auto] !px-[15px] max-w-full !mx-auto my-[300px]">
|
||||
{/* 新增模块标题和副标题显示 */}
|
||||
{(title || subtitle) && (
|
||||
<div className="relative z-20 text-center mb-10 select-text">
|
||||
{title && <h2 className="text-3xl font-bold mb-2">{title}</h2>}
|
||||
{subtitle && <p className="text-lg text-gray-500">{subtitle}</p>}
|
||||
</div>
|
||||
)}
|
||||
<div className={`blog classic-view grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-${columns} gap-6`}>
|
||||
{loading ? (
|
||||
<div className={`col-span-${columns} text-center py-12 text-gray-400`}>加载中...</div>
|
||||
@ -212,6 +219,17 @@ export default function CategoryItems({ pagetype = "", category = "", category_s
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
{/* 底部更多按钮 */}
|
||||
{category_slug && (
|
||||
<div className="text-center mt-8">
|
||||
<a
|
||||
href={`/${category_slug}`}
|
||||
className="btn btn-lg btn-fuchsia !text-white !bg-[#1A1A1A] hover:text-white hover:bg-[#1A1A1A] hover:!border-[#1A1A1A] active:text-white active:bg-[#1A1A1A] active:border-[#1A1A1A] disabled:text-white disabled:bg-[#1A1A1A] disabled:border-[#1A1A1A] !rounded-[0.8rem] mx-1"
|
||||
>
|
||||
更多
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -225,4 +243,6 @@ CategoryItems.propTypes = {
|
||||
]),
|
||||
columns: PropTypes.number,
|
||||
category_slug: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
subtitle: PropTypes.string,
|
||||
};
|
||||
|
||||
253
components/homes/home-15/SwiperItems.jsx
Normal file
253
components/homes/home-15/SwiperItems.jsx
Normal file
@ -0,0 +1,253 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { Navigation, Pagination, Grid } from "swiper/modules";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export default function SwiperItems({ pagetype = "", category = "", category_slug = "" , count = 8, columns = 4, rows = 1 }) {
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const params = new URLSearchParams({ pagetype });
|
||||
if (category) params.append("category", category);
|
||||
if (count !== undefined && count !== null) params.append("count", count);
|
||||
const res = await fetch(`/api/get-listview-data?${params.toString()}`);
|
||||
const json = await res.json();
|
||||
if (Array.isArray(json.data)) {
|
||||
setPosts(json.data);
|
||||
} else {
|
||||
setPosts([]);
|
||||
}
|
||||
} catch (e) {
|
||||
setPosts([]);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
fetchData();
|
||||
}, [pagetype, category, count]);
|
||||
|
||||
// 渲染卡片内容
|
||||
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 = 60) {
|
||||
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 + "...";
|
||||
}
|
||||
|
||||
// 日期格式化函数,支持自定义是否显示时分秒
|
||||
function formatDate(dateStr, options = {}) {
|
||||
if (!dateStr) return "";
|
||||
const d = new Date(dateStr);
|
||||
if (isNaN(d.getTime())) return dateStr;
|
||||
const { showTime = false } = options;
|
||||
const yyyy = d.getFullYear();
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
let result = `${yyyy}-${mm}-${dd}`;
|
||||
if (showTime) {
|
||||
const hh = String(d.getHours()).padStart(2, '0');
|
||||
const min = String(d.getMinutes()).padStart(2, '0');
|
||||
const ss = String(d.getSeconds()).padStart(2, '0');
|
||||
result += ` ${hh}:${min}:${ss}`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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="swiper-container blog classic-view !mb-[7rem] xl:!mb-[10rem] lg:!mb-[10rem] md:!mb-[10rem]">
|
||||
{loading ? (
|
||||
<div className={`text-center py-12 text-gray-400`}>加载中...</div>
|
||||
) : posts.length === 0 ? (
|
||||
<div className={`text-center py-12 text-gray-400`}>暂无数据</div>
|
||||
) : (
|
||||
<Swiper
|
||||
className="swiper"
|
||||
modules={[Navigation, Pagination, Grid]}
|
||||
spaceBetween={30}
|
||||
slidesPerView={columns}
|
||||
slidesPerGroup={columns * rows}
|
||||
grid={{ rows }}
|
||||
pagination={{ clickable: true, el: ".spdb15" }}
|
||||
navigation={{ prevEl: ".snbpb15", nextEl: ".snbnb15" }}
|
||||
breakpoints={{
|
||||
0: { slidesPerView: 1, grid: { rows: 1 } },
|
||||
575: { slidesPerView: 1, grid: { rows: 1 } },
|
||||
768: { slidesPerView: 2, grid: { rows: rows > 1 ? 2 : 1 } },
|
||||
992: { slidesPerView: columns, grid: { rows } },
|
||||
}}
|
||||
>
|
||||
{posts.map((post, idx) => (
|
||||
<SwiperSlide key={post.slug || post.id || idx} className="swiper-slide">
|
||||
<article className="post !mb-8">
|
||||
<div className="card">
|
||||
{/* 图片/轮播/视频部分 */}
|
||||
<figure className="card-img-top overlay overlay-1 hover-scale group">
|
||||
<Link href={post.slug ? `/${category_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={`/${category_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 ? `/${category_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>{formatDate(post.creation)}</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]">
|
||||
<i className="uil uil-user pr-[0.2rem] align-[-.05rem] before:content-['\\ed6f']" />
|
||||
<span>{post.author}</span>
|
||||
</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>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
)}
|
||||
<div className="swiper-controls">
|
||||
<div className="swiper-pagination spdb15"></div>
|
||||
<div className="swiper-button-prev snbpb15"></div>
|
||||
<div className="swiper-button-next snbnb15"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SwiperItems.propTypes = {
|
||||
pagetype: PropTypes.string,
|
||||
category: PropTypes.string,
|
||||
count: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]),
|
||||
columns: PropTypes.number,
|
||||
category_slug: PropTypes.string,
|
||||
rows: PropTypes.number,
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user