mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-06-07 07:26:46 +00:00
magzine 微调
This commit is contained in:
@@ -7,9 +7,9 @@ export default function CategoryItem({ selected, category, categoryCount }) {
|
|||||||
passHref
|
passHref
|
||||||
className={
|
className={
|
||||||
(selected
|
(selected
|
||||||
? ' bg-gray-600 text-white '
|
? 'bg-gray-600 text-white '
|
||||||
: 'dark:text-gray-400 text-gray-900 ') +
|
: 'dark:text-gray-400 text-gray-900 ') +
|
||||||
'text-sm hover:underline flex text-md items-center duration-300 cursor-pointer py-1 whitespace-nowrap'
|
'text-sm font-semibold hover:underline flex text-md items-center duration-300 cursor-pointer py-1 whitespace-nowrap'
|
||||||
}>
|
}>
|
||||||
<div>
|
<div>
|
||||||
{category} {categoryCount && `(${categoryCount})`}
|
{category} {categoryCount && `(${categoryCount})`}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { useRouter } from 'next/router'
|
|||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
const PostGroupLatest = props => {
|
const PostGroupLatest = props => {
|
||||||
const { latestPosts } = props
|
const { latestPosts, vertical } = props
|
||||||
// 获取当前路径
|
// 获取当前路径
|
||||||
const currentPath = useRouter().asPath
|
const currentPath = useRouter().asPath
|
||||||
const { locale, siteInfo } = useGlobal()
|
const { locale, siteInfo } = useGlobal()
|
||||||
@@ -23,12 +23,12 @@ const PostGroupLatest = props => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* 标题 */}
|
{/* 标题 */}
|
||||||
<div className=' mb-2 px-1 flex flex-nowrap justify-between'>
|
<div className='mb-2 px-1 flex flex-nowrap justify-between'>
|
||||||
<div className='font-bold text-lg'>{locale.COMMON.LATEST_POSTS}</div>
|
<div className='font-bold text-lg'>{locale.COMMON.LATEST_POSTS}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 文章列表 */}
|
{/* 文章列表 */}
|
||||||
<div className='grid grid-cols-1 lg:grid-cols-4'>
|
<div className={`grid grid-cols-1 ${!vertical ? 'lg:grid-cols-4' : ''}`}>
|
||||||
{latestPosts.map(post => {
|
{latestPosts.map(post => {
|
||||||
const selected =
|
const selected =
|
||||||
currentPath === `${siteConfig('SUB_PATH', '')}/${post.slug}`
|
currentPath === `${siteConfig('SUB_PATH', '')}/${post.slug}`
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import LazyImage from '@/components/LazyImage'
|
|||||||
import NotionIcon from '@/components/NotionIcon'
|
import NotionIcon from '@/components/NotionIcon'
|
||||||
import { siteConfig } from '@/lib/config'
|
import { siteConfig } from '@/lib/config'
|
||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
|
import { formatDateFmt } from '@/lib/utils/formatDate'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import CategoryItem from './CategoryItem'
|
import CategoryItem from './CategoryItem'
|
||||||
|
|
||||||
@@ -14,19 +15,19 @@ const PostItemCard = ({ post }) => {
|
|||||||
const cover = post?.pageCoverThumbnail || siteInfo?.pageCover
|
const cover = post?.pageCoverThumbnail || siteInfo?.pageCover
|
||||||
return (
|
return (
|
||||||
<div key={post.id} className='mb-6 max-w-screen-2xl'>
|
<div key={post.id} className='mb-6 max-w-screen-2xl'>
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col space-y-3'>
|
||||||
{siteConfig('MAGZINE_POST_LIST_COVER') && (
|
{siteConfig('MAGZINE_POST_LIST_COVER') && (
|
||||||
<Link
|
<Link
|
||||||
href={post?.href}
|
href={post?.href}
|
||||||
passHref
|
passHref
|
||||||
className={
|
className={
|
||||||
'cursor-pointer hover:underline leading-tight text-gray-700 dark:text-gray-300 hover:text-gray-500 dark:hover:text-gray-400'
|
'cursor-pointer hover:underline leading-tight text-gray-700 dark:text-gray-300 hover:text-gray-500 dark:hover:text-gray-400'
|
||||||
}>
|
}>
|
||||||
<div className='w-full h-40 aspect-video overflow-hidden mb-2'>
|
<div className='w-full h-40 aspect-video overflow-hidden mb-2'>
|
||||||
<LazyImage
|
<LazyImage
|
||||||
src={cover}
|
src={cover}
|
||||||
style={cover ? {} : { height: '0px' }}
|
style={cover ? {} : { height: '0px' }}
|
||||||
className='w-full h-40 aspect-video object-cover hover:scale-125 duration-150'
|
className='w-full h-40 aspect-video object-cover'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -39,7 +40,7 @@ const PostItemCard = ({ post }) => {
|
|||||||
href={post?.href}
|
href={post?.href}
|
||||||
passHref
|
passHref
|
||||||
className={
|
className={
|
||||||
'text-lg cursor-pointer hover:underline leading-tight text-gray-700 dark:text-gray-300 hover:text-gray-500 dark:hover:text-gray-400'
|
'text-xl cursor-pointer hover:underline leading-tight text-gray-700 dark:text-gray-300 hover:text-gray-500 dark:hover:text-gray-400'
|
||||||
}>
|
}>
|
||||||
<h2>
|
<h2>
|
||||||
{siteConfig('POST_TITLE_ICON') && (
|
{siteConfig('POST_TITLE_ICON') && (
|
||||||
@@ -49,7 +50,9 @@ const PostItemCard = ({ post }) => {
|
|||||||
</h2>
|
</h2>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className='text-sm py-1'>{post.date?.start_date}</div>
|
<div className='text-sm'>
|
||||||
|
{formatDateFmt(post.publishDate, 'yyyy-MM')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import NotionIcon from '@/components/NotionIcon'
|
import NotionIcon from '@/components/NotionIcon'
|
||||||
import { siteConfig } from '@/lib/config'
|
import { siteConfig } from '@/lib/config'
|
||||||
import { useGlobal } from '@/lib/global'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import CategoryItem from './CategoryItem'
|
import CategoryItem from './CategoryItem'
|
||||||
|
|
||||||
@@ -10,12 +9,10 @@ import CategoryItem from './CategoryItem'
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const PostItemCardSimple = ({ post, showSummary }) => {
|
const PostItemCardSimple = ({ post, showSummary }) => {
|
||||||
const showPreview = siteConfig('MAGZINE_POST_LIST_PREVIEW') && post.blockMap
|
|
||||||
const { locale } = useGlobal()
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={post.id}
|
key={post.id}
|
||||||
className='lg:mb-6 max-w-screen-2xl border-t mr-8 py-2 gap-y-4 flex flex-col dark:border-gray-800 '>
|
className='lg:mb-6 max-w-screen-2xl border-t border-gray-300 mr-8 py-2 gap-y-3 flex flex-col dark:border-gray-800 '>
|
||||||
<div className='flex mr-2 items-center'>
|
<div className='flex mr-2 items-center'>
|
||||||
{siteConfig('MAGZINE_POST_LIST_CATEGORY') && (
|
{siteConfig('MAGZINE_POST_LIST_CATEGORY') && (
|
||||||
<CategoryItem category={post.category} />
|
<CategoryItem category={post.category} />
|
||||||
@@ -35,7 +32,7 @@ const PostItemCardSimple = ({ post, showSummary }) => {
|
|||||||
</h2>
|
</h2>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className='text-sm py-2 text-gray-700'>{post.date?.start_date}</div>
|
<div className='text-sm text-gray-700'>{post.date?.start_date}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import PostItemCard from './PostItemCard'
|
import PostItemCard from './PostItemCard'
|
||||||
import PostListEmpty from './PostListEmpty'
|
import PostListEmpty from './PostListEmpty'
|
||||||
|
import Swiper from './Swiper'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 博文水平列表
|
* 博文水平列表
|
||||||
@@ -15,22 +16,34 @@ const PostListHorizontal = ({ title, href, posts, hasBg }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`w-full py-10 px-2 lg:px-0 ${hasBg ? 'bg-[#F6F6F1] dark:bg-black' : ''}`}>
|
<div
|
||||||
|
className={`w-full py-10 px-2 lg:px-0 ${hasBg ? 'bg-[#F6F6F1] dark:bg-black' : ''}`}>
|
||||||
<div className='max-w-screen-2xl w-full mx-auto'>
|
<div className='max-w-screen-2xl w-full mx-auto'>
|
||||||
{/* 标题 */}
|
{/* 标题 */}
|
||||||
<div className='flex justify-between items-center py-6'>
|
<div className='flex justify-between items-center py-6'>
|
||||||
<h3 className='text-2xl'>{title}</h3>
|
<h3 className='text-2xl'>{title}</h3>
|
||||||
<Link className='text-lg underline' href={href}>
|
{href && (
|
||||||
<span>查看全部</span>
|
<Link className='hidden lg:block text-lg underline' href={href}>
|
||||||
<i className='ml-2 fas fa-arrow-right' />
|
<span>查看全部</span>
|
||||||
</Link>
|
<i className='ml-2 fas fa-arrow-right' />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* 列表 */}
|
{/* 列表 */}
|
||||||
<ul className='grid grid-cols-1 lg:grid-cols-4 gap-4'>
|
<ul className='hidden lg:grid grid-cols-1 lg:grid-cols-4 gap-4'>
|
||||||
{posts?.map((p, index) => {
|
{posts?.map((p, index) => {
|
||||||
return <PostItemCard key={index} post={p} />
|
return <PostItemCard key={index} post={p} />
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
<div className='block lg:hidden px-2'>
|
||||||
|
<Swiper posts={posts} />
|
||||||
|
{href && (
|
||||||
|
<Link className='lg:hidden block text-lg underline' href={href}>
|
||||||
|
<span>查看全部</span>
|
||||||
|
<i className='ml-2 fas fa-arrow-right' />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { siteConfig } from '@/lib/config'
|
import { siteConfig } from '@/lib/config'
|
||||||
import PostItemCard from './PostItemCard'
|
import PostItemCard from './PostItemCard'
|
||||||
import PostListEmpty from './PostListEmpty'
|
import PostListEmpty from './PostListEmpty'
|
||||||
|
import Swiper from './Swiper'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 博文水平列表
|
* 博文水平列表
|
||||||
@@ -25,11 +26,14 @@ const PostListRecommend = ({ latestPosts, allNavPages }) => {
|
|||||||
<h3 className='text-4xl font-bold'>{title}</h3>
|
<h3 className='text-4xl font-bold'>{title}</h3>
|
||||||
</div>
|
</div>
|
||||||
{/* 列表 */}
|
{/* 列表 */}
|
||||||
<ul className='flex flex-col lg:flex-row gap-4 lg:overflow-x-scroll'>
|
<ul className='hidden lg:grid grid-cols-1 lg:grid-cols-4 gap-4'>
|
||||||
{recommendPosts?.map(p => {
|
{recommendPosts?.map((p, index) => {
|
||||||
return <PostItemCard key={p.id} post={p} />
|
return <PostItemCard key={index} post={p} />
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
<div className='block lg:hidden px-2'>
|
||||||
|
<Swiper posts={recommendPosts} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
137
themes/magzine/components/Swiper.js
Normal file
137
themes/magzine/components/Swiper.js
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { useRef, useState } from 'react'
|
||||||
|
import PostItemCard from './PostItemCard'
|
||||||
|
|
||||||
|
const Swiper = ({ posts }) => {
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0)
|
||||||
|
const containerRef = useRef(null)
|
||||||
|
|
||||||
|
// 用于记录触摸开始和结束的水平位置
|
||||||
|
const touchStartPos = useRef({ x: 0, y: 0 })
|
||||||
|
const touchEndPos = useRef({ x: 0, y: 0 })
|
||||||
|
const isHorizontalSwipe = useRef(false)
|
||||||
|
|
||||||
|
const handleTouchStart = e => {
|
||||||
|
// 记录初始触摸位置
|
||||||
|
touchStartPos.current = {
|
||||||
|
x: e.touches[0].clientX,
|
||||||
|
y: e.touches[0].clientY
|
||||||
|
}
|
||||||
|
isHorizontalSwipe.current = false // 重置滑动方向标志
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTouchMove = e => {
|
||||||
|
const touch = e.touches[0]
|
||||||
|
const deltaX = touch.clientX - touchStartPos.current.x
|
||||||
|
const deltaY = touch.clientY - touchStartPos.current.y
|
||||||
|
|
||||||
|
// 判断是否为水平滑动(避免垂直滑动干扰)
|
||||||
|
if (!isHorizontalSwipe.current) {
|
||||||
|
isHorizontalSwipe.current = Math.abs(deltaX) > Math.abs(deltaY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是水平滑动,阻止垂直滚动
|
||||||
|
if (isHorizontalSwipe.current) {
|
||||||
|
e.preventDefault() // 阻止垂直方向的默认滚动行为
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTouchEnd = e => {
|
||||||
|
if (isHorizontalSwipe.current) {
|
||||||
|
// 记录触摸结束位置
|
||||||
|
touchEndPos.current = {
|
||||||
|
x: e.changedTouches[0].clientX,
|
||||||
|
y: e.changedTouches[0].clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算滑动距离
|
||||||
|
const deltaX = touchEndPos.current.x - touchStartPos.current.x
|
||||||
|
|
||||||
|
// 如果滑动距离足够大,则决定滑动到下一张或上一张卡片
|
||||||
|
const swipeThreshold = 50 // 设置滑动的阈值
|
||||||
|
if (deltaX > swipeThreshold) {
|
||||||
|
goToPrevious() // 向右滑动,上一张
|
||||||
|
} else if (deltaX < -swipeThreshold) {
|
||||||
|
goToNext() // 向左滑动,下一张
|
||||||
|
} else {
|
||||||
|
// 滑动距离不够,回到当前卡片
|
||||||
|
scrollToCard(currentIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToPrevious = () => {
|
||||||
|
const newIndex = currentIndex === 0 ? posts.length - 1 : currentIndex - 1
|
||||||
|
setCurrentIndex(newIndex)
|
||||||
|
scrollToCard(newIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToNext = () => {
|
||||||
|
const newIndex = currentIndex === posts.length - 1 ? 0 : currentIndex + 1
|
||||||
|
setCurrentIndex(newIndex)
|
||||||
|
scrollToCard(newIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToCard = index => {
|
||||||
|
const container = containerRef.current
|
||||||
|
if (!container) return
|
||||||
|
|
||||||
|
const cardWidth = container.scrollWidth / posts.length
|
||||||
|
container.scrollTo({
|
||||||
|
left: index * cardWidth,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleIndicatorClick = index => {
|
||||||
|
setCurrentIndex(index)
|
||||||
|
scrollToCard(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='relative w-full mx-auto'>
|
||||||
|
{/* 左侧点击区域 */}
|
||||||
|
<div
|
||||||
|
className='absolute inset-y-0 left-0 w-1/5 z-10'
|
||||||
|
onClick={goToPrevious}></div>
|
||||||
|
|
||||||
|
{/* 右侧点击区域 */}
|
||||||
|
<div
|
||||||
|
className='absolute inset-y-0 right-0 w-1/5 z-10'
|
||||||
|
onClick={goToNext}></div>
|
||||||
|
|
||||||
|
{/* Swiper Container */}
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className='relative w-full overflow-x-scroll scroll-smooth py-4'
|
||||||
|
onTouchStart={handleTouchStart}
|
||||||
|
onTouchMove={handleTouchMove}
|
||||||
|
onTouchEnd={handleTouchEnd}
|
||||||
|
style={{ WebkitOverflowScrolling: 'touch' }} // iOS自然滚动支持
|
||||||
|
>
|
||||||
|
<div className='flex gap-x-4'>
|
||||||
|
{posts.map((item, index) => (
|
||||||
|
<div key={index} className='w-5/6 flex-shrink-0'>
|
||||||
|
<PostItemCard key={index} post={item} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Indicator Dots */}
|
||||||
|
<div className='absolute bottom-0 left-0 right-0 flex justify-center space-x-2'>
|
||||||
|
{posts.map((_, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => handleIndicatorClick(index)}
|
||||||
|
className={`w-3 h-3 rounded-full ${
|
||||||
|
currentIndex === index
|
||||||
|
? 'bg-black dark:bg-white'
|
||||||
|
: 'bg-gray-300 dark:bg-gray-700'
|
||||||
|
}`}></button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Swiper
|
||||||
@@ -178,7 +178,7 @@ const LayoutSlug = props => {
|
|||||||
|
|
||||||
{/* 文章区块分为三列 */}
|
{/* 文章区块分为三列 */}
|
||||||
<div className='grid grid-cols-1 lg:grid-cols-5 gap-8 py-12'>
|
<div className='grid grid-cols-1 lg:grid-cols-5 gap-8 py-12'>
|
||||||
<div className='h-full lg:col-span-1 hidden lg:contents'>
|
<div className='h-full lg:col-span-1 hidden lg:block'>
|
||||||
<Catalog toc={post?.toc} className='sticky top-20' />
|
<Catalog toc={post?.toc} className='sticky top-20' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ const LayoutSlug = props => {
|
|||||||
|
|
||||||
{/* 最新文章区块 */}
|
{/* 最新文章区块 */}
|
||||||
<div>
|
<div>
|
||||||
<PostGroupLatest {...props} />
|
<PostGroupLatest {...props} vertical={true} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 文章分类区块 */}
|
{/* 文章分类区块 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user