theme-photo

This commit is contained in:
tangly1024.com
2024-11-04 16:11:27 +08:00
parent c43924db77
commit 67019e9477
9 changed files with 169 additions and 180 deletions

View File

@@ -65,8 +65,8 @@ export async function getNotionPageData({ pageId, from }) {
const cacheKey = 'page_block_' + pageId const cacheKey = 'page_block_' + pageId
let data = await getDataFromCache(cacheKey) let data = await getDataFromCache(cacheKey)
if (data && data.pageIds?.length > 0) { if (data && data.pageIds?.length > 0) {
// console.log('[API<<--缓存]', `from:${from}`, `root-page-id:${pageId}`) console.debug('[API<<--缓存]', `from:${from}`, `root-page-id:${pageId}`)
// return data return data
} else { } else {
// 从接口读取 // 从接口读取
data = await getDataBaseInfoByNotionAPI({ pageId, from }) data = await getDataBaseInfoByNotionAPI({ pageId, from })

View File

@@ -2,6 +2,10 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
html {
overflow-x: hidden;
}
.wrapper { .wrapper {
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;

View File

@@ -9,7 +9,7 @@ import MenuHierarchical from './MenuHierarchical'
export const Header = props => { export const Header = props => {
return ( return (
<> <>
<header className='w-full px-8 h-20 z-20 flex lg:flex-row md:flex-col justify-center items-center'> <header className='w-full px-8 h-20 z-30 flex lg:flex-row md:flex-col justify-center items-center'>
{/* 左侧Logo */} {/* 左侧Logo */}
<Link <Link
href='/' href='/'
@@ -18,7 +18,7 @@ export const Header = props => {
</Link> </Link>
{/* 右侧使用一个三级菜单 */} {/* 右侧使用一个三级菜单 */}
<div className='md:w-auto text-center flex space-x-2'> <div className='ml-6 mt-7'>
<MenuHierarchical {...props} /> <MenuHierarchical {...props} />
</div> </div>
</header> </header>

View File

@@ -2,7 +2,7 @@ import Collapse from '@/components/Collapse'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useState } from 'react' import { useEffect, useState } from 'react'
import { usePhotoGlobal } from '..' import { usePhotoGlobal } from '..'
import CONFIG from '../config' import CONFIG from '../config'
import { MenuItemCollapse } from './MenuItemCollapse' import { MenuItemCollapse } from './MenuItemCollapse'
@@ -20,6 +20,9 @@ export default function MenuHierarchical(props) {
const toggleMenuOpen = () => { const toggleMenuOpen = () => {
setIsOpen(!isOpen) setIsOpen(!isOpen)
} }
const closeModal = () => {
setIsOpen(false)
}
let links = [ let links = [
{ {
id: 1, id: 1,
@@ -59,19 +62,31 @@ export default function MenuHierarchical(props) {
links = customMenu links = customMenu
} }
const [title, setTitle] = useState(siteConfig('BIO'))
useEffect(() => {
const currentLink = links.find(link => link.href === router.pathname)
if (currentLink) {
setTitle(currentLink.name)
}
closeModal()
}, [router])
return ( return (
<div className='absolute top-0'> <div className='absolute top-0 mt-7'>
{/* 菜单按钮 */} {/* 菜单按钮 */}
<div onClick={toggleMenuOpen} className='w-8 cursor-pointer'> <div
{isOpen ? ( onClick={toggleMenuOpen}
<i className='fas fa-times' /> className=' whitespace-nowrap cursor-pointer'>
) : ( {title}
<i className='fas fa-bars' />
)}
</div> </div>
<Collapse collapseRef={collapseRef} type='vertical' isOpen={isOpen}> <Collapse
className='z-50'
collapseRef={collapseRef}
type='vertical'
isOpen={isOpen}>
{/* 移动端菜单 */} {/* 移动端菜单 */}
<menu id='nav-menu-mobile' className='my-auto justify-start'> <menu id='nav-menu-mobile' className='my-4 space-y-4 justify-start'>
{links?.map( {links?.map(
(link, index) => (link, index) =>
link && link &&
@@ -87,6 +102,13 @@ export default function MenuHierarchical(props) {
)} )}
</menu> </menu>
</Collapse> </Collapse>
{/* 遮罩 */}
{isOpen && (
<div
onClick={closeModal}
className='-z-10 fixed top-0 left-0 w-full h-full flex items-center justify-center bg-glassmorphism'
/>
)}
</div> </div>
) )
} }

View File

@@ -28,14 +28,12 @@ export const MenuItemCollapse = props => {
return ( return (
<> <>
<div <div className='select-none w-full text-left ' onClick={toggleShow}>
className='select-none w-full px-6 py-2 text-left '
onClick={toggleShow}>
{!hasSubMenu && ( {!hasSubMenu && (
<Link <Link
href={link?.href} href={link?.href}
target={link?.target} target={link?.target}
className='flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest'> className='flex justify-between dark:text-gray-200 no-underline tracking-widest'>
<span className=' transition-all items-center duration-200'> <span className=' transition-all items-center duration-200'>
{link?.icon && <i className={link.icon + ' mr-4'} />} {link?.icon && <i className={link.icon + ' mr-4'} />}
{link?.name} {link?.name}
@@ -45,7 +43,7 @@ export const MenuItemCollapse = props => {
{hasSubMenu && ( {hasSubMenu && (
<div <div
onClick={hasSubMenu ? toggleOpenSubMenu : null} onClick={hasSubMenu ? toggleOpenSubMenu : null}
className='flex items-center justify-between pl-2 pr-4 cursor-pointer dark:text-gray-200 no-underline tracking-widest'> className='flex items-center justify-between cursor-pointer dark:text-gray-200 no-underline tracking-widest'>
<span className='transition-all items-center duration-200'> <span className='transition-all items-center duration-200'>
{link?.icon && <i className={link.icon + ' mr-4'} />} {link?.icon && <i className={link.icon + ' mr-4'} />}
{link?.name} {link?.name}
@@ -66,9 +64,9 @@ export const MenuItemCollapse = props => {
return ( return (
<div <div
key={index} key={index}
className='dark:text-gray-200 text-left px-3 justify-start tracking-widest transition-all duration-200 py-3 pr-6'> className='dark:text-gray-200 text-left px-3 justify-start py-1 tracking-widest transition-all duration-200 pr-6'>
<Link href={sLink.href} target={link?.target}> <Link href={sLink.href} target={link?.target}>
<span className='text-sm ml-4 whitespace-nowrap'> <span className='ml-4 whitespace-nowrap'>
{link?.icon && <i className={sLink.icon + ' mr-2'} />}{' '} {link?.icon && <i className={sLink.icon + ' mr-2'} />}{' '}
{sLink.title} {sLink.title}
</span> </span>

View File

@@ -3,51 +3,49 @@ 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 { formatDateFmt } from '@/lib/utils/formatDate'
import Link from 'next/link'
/** /**
* 普通的博客卡牌 * 普通的博客卡牌
* 带封面图 * 带封面图
*/ */
const PostItemCard = ({ post }) => { const PostItemCard = ({ post, className }) => {
const { siteInfo } = useGlobal() const { siteInfo } = useGlobal()
const cover = post?.pageCoverThumbnail || siteInfo?.pageCover const cover = post?.pageCoverThumbnail || siteInfo?.pageCover
return ( return (
<div key={post.id} className='mb-6 max-w-screen-3xl'> <div key={post?.id} className={className}>
<div className='flex flex-col space-y-3 relative'> <div className='space-y-3 relative justify-center items-center text-gray-500'>
{/* <Link <div className='h-full overflow-hidden'>
href={post?.href}
passHref
className={
'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-3/4 aspect-video overflow-hidden mb-2'>
<LazyImage <LazyImage
alt={post?.title} alt={post?.title}
src={cover} src={cover}
style={cover ? {} : { height: '0px' }} style={cover ? {} : { height: '0px' }}
className='w-full h-3/4 aspect-video object-cover select-none pointer-events-none' className='h-full max-h-[70vh] object-cover select-none pointer-events-none'
/> />
</div> </div>
{/* </Link> */}
<div className='absolute bottom-0'> <div className='text-center'>
{/* <Link <Link
href={post?.href} href={post?.href}
passHref passHref
className={ className={
'text-xl 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 dark:text-gray-300 '
}> */} }>
<h2 className='select-none pointer-events-none'> <h2 className='select-none pointer-events-none'>
{siteConfig('POST_TITLE_ICON') && ( {siteConfig('POST_TITLE_ICON') && (
<NotionIcon icon={post.pageIcon} /> <NotionIcon icon={post?.pageIcon} />
)} )}
{post.title} {post?.title}
</h2> </h2>
{/* </Link> */} </Link>
<div className='text-sm select-none pointer-events-none'> {/* 发布日期 */}
{formatDateFmt(post.publishDate, 'yyyy-MM')} <Link
</div> className='text-sm'
href={`/archive#${formatDateFmt(post?.publishDate, 'yyyy-MM')}`}
passHref>
{formatDateFmt(post?.publishDate, 'yyyy-MM')}
</Link>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,111 +1,101 @@
import { useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import PostItemCard from './PostItemCard' import PostItemCard from './PostItemCard'
const Swiper = ({ posts }) => { const InertiaCarousel = ({ posts }) => {
const [currentIndex, setCurrentIndex] = useState(0) const carouselRef = useRef(null)
const containerRef = useRef(null) const [isDragging, setIsDragging] = useState(false)
const [startX, setStartX] = useState(0)
const [scrollLeft, setScrollLeft] = useState(0)
const [lastX, setLastX] = useState(0) // 上一次的位置
const [velocity, setVelocity] = useState(0)
const animationRef = useRef(null)
const touchStartPos = useRef({ x: 0, y: 0 }) // 开始拖拽事件
const isDragging = useRef(false) const startDrag = e => {
const scrollStartLeft = useRef(0) // 记录拖拽开始时的滚动位置 e.preventDefault()
setIsDragging(true)
// 处理鼠标和触摸开始事件 const startPosition = e.pageX || e.touches[0].pageX
const handleDragStart = e => { setStartX(startPosition - carouselRef.current.offsetLeft)
const x = e.touches ? e.touches[0].clientX : e.clientX setScrollLeft(carouselRef.current.scrollLeft)
touchStartPos.current = { x } setLastX(startPosition) // 初始化上一次的位置
isDragging.current = true cancelInertiaScroll() // 停止任何正在进行的惯性动画
scrollStartLeft.current = containerRef.current.scrollLeft
// 更新鼠标样式
containerRef.current.style.cursor = 'grabbing'
} }
// 处理鼠标和触摸移动事件 // 拖拽中事件
const handleDragMove = e => { const duringDrag = e => {
if (!isDragging.current) return if (!isDragging) return
e.preventDefault()
const currentPosition = e.pageX || e.touches[0].pageX
const distance = currentPosition - startX
carouselRef.current.scrollLeft = scrollLeft - distance
const x = e.touches ? e.touches[0].clientX : e.clientX // 计算当前速度
const deltaX = touchStartPos.current.x - x const deltaX = currentPosition - lastX
setVelocity(deltaX) // 更新速度
// 根据拖动的距离更新滚动位置 setLastX(currentPosition) // 更新 lastX 为当前位置
containerRef.current.scrollLeft = scrollStartLeft.current + deltaX
} }
// 处理鼠标和触摸结束事件 // 结束拖拽事件,启动惯性滚动
const handleDragEnd = () => { const endDrag = () => {
isDragging.current = false setIsDragging(false)
containerRef.current.style.cursor = 'grab' startInertiaScroll(velocity) // 根据最终速度启动惯性滚动
} }
// 处理指示器点击事件 // 惯性滚动函数
const handleIndicatorClick = index => { const startInertiaScroll = initialVelocity => {
setCurrentIndex(index) let currentVelocity = initialVelocity
scrollToCard(index) const decay = 0.95 // 惯性衰减系数
const animate = () => {
if (Math.abs(currentVelocity) > 0.5) {
// 仅当速度足够大时继续滚动
carouselRef.current.scrollLeft -= currentVelocity
currentVelocity *= decay // 速度衰减
animationRef.current = requestAnimationFrame(animate)
} else {
cancelAnimationFrame(animationRef.current)
}
}
animate()
} }
// 滚动到特定卡片 // 取消惯性滚动
const scrollToCard = index => { const cancelInertiaScroll = () => {
const container = containerRef.current if (animationRef.current) {
if (!container) return cancelAnimationFrame(animationRef.current)
const cardWidth = container.scrollWidth / posts.length }
container.scrollTo({
left: index * cardWidth - cardWidth / 6, // 调整位置以居中
behavior: 'smooth'
})
} }
useEffect(() => {
return () => cancelInertiaScroll() // 清除动画
}, [])
return ( return (
<div className='relative w-full mx-auto px-12 my-8'> <div
<div ref={carouselRef}
className='absolute inset-y-0 left-0 w-12 z-10 cursor-pointer bg-black hover:opacity-20 opacity-10 duration-100' className={`flex w-screen overflow-x-auto space-x-6 ${
onClick={() => isDragging ? 'cursor-grabbing' : 'cursor-grab'
handleIndicatorClick( }`}
currentIndex === 0 ? posts.length - 1 : currentIndex - 1 onMouseDown={startDrag}
) onMouseMove={duringDrag}
}></div> onMouseUp={endDrag}
onMouseLeave={endDrag}
onTouchStart={startDrag}
onTouchMove={duringDrag}
onTouchEnd={endDrag}
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
{/* Carousel items */}
<div <div className='min-w-[33vw]' />
className='absolute inset-y-0 right-0 w-12 z-10 cursor-pointer bg-black hover:opacity-20 opacity-10 duration-100' {posts &&
onClick={() => posts?.map((post, index) => (
handleIndicatorClick( <PostItemCard
currentIndex === posts.length - 1 ? 0 : currentIndex + 1 className='min-w-[33vw] flex items-center justify-center'
)
}></div>
<div
ref={containerRef}
className='relative w-full overflow-x-hidden py-4 cursor-grab'
onTouchStart={handleDragStart}
onTouchMove={handleDragMove}
onTouchEnd={handleDragEnd}
onMouseDown={handleDragStart}
onMouseMove={handleDragMove}
onMouseUp={handleDragEnd}
onMouseLeave={handleDragEnd}
style={{ WebkitOverflowScrolling: 'touch' }}>
<div className='flex gap-x-4 transition-transform'>
{posts.map((item, index) => (
<div key={index} className='w-3/4 flex-shrink-0'>
<PostItemCard post={item} />
</div>
))}
</div>
</div>
<div className='absolute bottom-0 left-0 right-0 flex justify-center space-x-2'>
{posts.map((_, index) => (
<button
key={index} key={index}
onClick={() => handleIndicatorClick(index)} post={post}
className={`w-3 h-3 rounded-full ${ />
currentIndex === index
? 'bg-black dark:bg-white'
: 'bg-gray-300 dark:bg-gray-700'
}`}></button>
))} ))}
</div>
</div> </div>
) )
} }
export default Swiper export default InertiaCarousel

View File

@@ -65,7 +65,7 @@ const LayoutBase = props => {
<ThemeGlobalPhoto.Provider <ThemeGlobalPhoto.Provider
value={{ searchModal, expandMenu, updateExpandMenu, collapseRef }}> value={{ searchModal, expandMenu, updateExpandMenu, collapseRef }}>
<div <div
id='theme-movie' id='theme-photo'
className={`${siteConfig('FONT_STYLE')} dark:text-gray-300 duration-300 transition-all bg-white dark:bg-[#2A2A2A] scroll-smooth min-h-screen flex flex-col justify-between`}> className={`${siteConfig('FONT_STYLE')} dark:text-gray-300 duration-300 transition-all bg-white dark:bg-[#2A2A2A] scroll-smooth min-h-screen flex flex-col justify-between`}>
<Style /> <Style />
@@ -133,7 +133,7 @@ const LayoutIndex = props => {
*/ */
const LayoutPostList = props => { const LayoutPostList = props => {
return ( return (
<div className='max-w-[90rem] mx-auto'> <div className='mx-auto'>
<SlotBar {...props} /> <SlotBar {...props} />
{/* 滑动组件 */} {/* 滑动组件 */}
<Swiper {...props} /> <Swiper {...props} />
@@ -480,15 +480,14 @@ const LayoutTagIndex = props => {
} }
export { export {
Layout404, Layout404,
LayoutArchive, LayoutArchive,
LayoutBase, LayoutBase,
LayoutCategoryIndex, LayoutCategoryIndex,
LayoutIndex, LayoutIndex,
LayoutPostList, LayoutPostList,
LayoutSearch, LayoutSearch,
LayoutSlug, LayoutSlug,
LayoutTagIndex, LayoutTagIndex,
CONFIG as THEME_CONFIG CONFIG as THEME_CONFIG
} }

View File

@@ -11,39 +11,17 @@ const Style = () => {
.dark body { .dark body {
background-color: black; background-color: black;
} }
// 毛玻璃背景色
.shadow-movie { .bg-glassmorphism {
box-shadow: background: hsla(0, 0%, 100%, 0.4);
0 26px 58px 0 rgba(0, 0, 0, 0.22), -webkit-backdrop-filter: blur(10px);
0 5px 14px 0 rgba(0, 0, 0, 0.18); backdrop-filter: blur(10px);
} }
// 视频聚合走马灯 .dark .bg-glassmorphism {
.notion-carousel { background: hsla(0, 0%, 0%, 0.4);
width: 100%; /* 根据需要调整 */ -webkit-backdrop-filter: blur(10px);
overflow: hidden; backdrop-filter: blur(10px);
}
.notion-carousel-wrapper .notion-carousel {
display: none;
}
.notion-carousel-wrapper .notion-carousel.active {
display: block;
}
.notion-carousel-route div {
cursor: pointer;
margin-bottom: 0.2rem;
}
.notion-carousel-route div:hover {
text-decoration: underline;
}
.notion-carousel div {
height: auto !important;
aspect-ratio: 2/1 !important;
} }
`}</style> `}</style>
) )