mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-29 15:10:06 +00:00
theme-photo
This commit is contained in:
@@ -9,7 +9,7 @@ import MenuHierarchical from './MenuHierarchical'
|
||||
export const Header = props => {
|
||||
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 */}
|
||||
<Link
|
||||
href='/'
|
||||
@@ -18,7 +18,7 @@ export const Header = props => {
|
||||
</Link>
|
||||
|
||||
{/* 右侧使用一个三级菜单 */}
|
||||
<div className='md:w-auto text-center flex space-x-2'>
|
||||
<div className='ml-6 mt-7'>
|
||||
<MenuHierarchical {...props} />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -2,7 +2,7 @@ import Collapse from '@/components/Collapse'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { usePhotoGlobal } from '..'
|
||||
import CONFIG from '../config'
|
||||
import { MenuItemCollapse } from './MenuItemCollapse'
|
||||
@@ -20,6 +20,9 @@ export default function MenuHierarchical(props) {
|
||||
const toggleMenuOpen = () => {
|
||||
setIsOpen(!isOpen)
|
||||
}
|
||||
const closeModal = () => {
|
||||
setIsOpen(false)
|
||||
}
|
||||
let links = [
|
||||
{
|
||||
id: 1,
|
||||
@@ -59,19 +62,31 @@ export default function MenuHierarchical(props) {
|
||||
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 (
|
||||
<div className='absolute top-0'>
|
||||
<div className='absolute top-0 mt-7'>
|
||||
{/* 菜单按钮 */}
|
||||
<div onClick={toggleMenuOpen} className='w-8 cursor-pointer'>
|
||||
{isOpen ? (
|
||||
<i className='fas fa-times' />
|
||||
) : (
|
||||
<i className='fas fa-bars' />
|
||||
)}
|
||||
<div
|
||||
onClick={toggleMenuOpen}
|
||||
className=' whitespace-nowrap cursor-pointer'>
|
||||
{title}
|
||||
</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(
|
||||
(link, index) =>
|
||||
link &&
|
||||
@@ -87,6 +102,13 @@ export default function MenuHierarchical(props) {
|
||||
)}
|
||||
</menu>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,14 +28,12 @@ export const MenuItemCollapse = props => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='select-none w-full px-6 py-2 text-left '
|
||||
onClick={toggleShow}>
|
||||
<div className='select-none w-full text-left ' onClick={toggleShow}>
|
||||
{!hasSubMenu && (
|
||||
<Link
|
||||
href={link?.href}
|
||||
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'>
|
||||
{link?.icon && <i className={link.icon + ' mr-4'} />}
|
||||
{link?.name}
|
||||
@@ -45,7 +43,7 @@ export const MenuItemCollapse = props => {
|
||||
{hasSubMenu && (
|
||||
<div
|
||||
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'>
|
||||
{link?.icon && <i className={link.icon + ' mr-4'} />}
|
||||
{link?.name}
|
||||
@@ -66,9 +64,9 @@ export const MenuItemCollapse = props => {
|
||||
return (
|
||||
<div
|
||||
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}>
|
||||
<span className='text-sm ml-4 whitespace-nowrap'>
|
||||
<span className='ml-4 whitespace-nowrap'>
|
||||
{link?.icon && <i className={sLink.icon + ' mr-2'} />}{' '}
|
||||
{sLink.title}
|
||||
</span>
|
||||
|
||||
@@ -3,51 +3,49 @@ import NotionIcon from '@/components/NotionIcon'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { formatDateFmt } from '@/lib/utils/formatDate'
|
||||
import Link from 'next/link'
|
||||
|
||||
/**
|
||||
* 普通的博客卡牌
|
||||
* 带封面图
|
||||
*/
|
||||
const PostItemCard = ({ post }) => {
|
||||
const PostItemCard = ({ post, className }) => {
|
||||
const { siteInfo } = useGlobal()
|
||||
const cover = post?.pageCoverThumbnail || siteInfo?.pageCover
|
||||
return (
|
||||
<div key={post.id} className='mb-6 max-w-screen-3xl'>
|
||||
<div className='flex flex-col space-y-3 relative'>
|
||||
{/* <Link
|
||||
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'>
|
||||
<div key={post?.id} className={className}>
|
||||
<div className='space-y-3 relative justify-center items-center text-gray-500'>
|
||||
<div className='h-full overflow-hidden'>
|
||||
<LazyImage
|
||||
alt={post?.title}
|
||||
src={cover}
|
||||
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>
|
||||
{/* </Link> */}
|
||||
|
||||
<div className='absolute bottom-0'>
|
||||
{/* <Link
|
||||
<div className='text-center'>
|
||||
<Link
|
||||
href={post?.href}
|
||||
passHref
|
||||
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'
|
||||
}> */}
|
||||
<h2 className='select-none pointer-events-none'>
|
||||
{siteConfig('POST_TITLE_ICON') && (
|
||||
<NotionIcon icon={post.pageIcon} />
|
||||
)}
|
||||
{post.title}
|
||||
</h2>
|
||||
{/* </Link> */}
|
||||
'cursor-pointer hover:underline leading-tight dark:text-gray-300 '
|
||||
}>
|
||||
<h2 className='select-none pointer-events-none'>
|
||||
{siteConfig('POST_TITLE_ICON') && (
|
||||
<NotionIcon icon={post?.pageIcon} />
|
||||
)}
|
||||
{post?.title}
|
||||
</h2>
|
||||
</Link>
|
||||
|
||||
<div className='text-sm select-none pointer-events-none'>
|
||||
{formatDateFmt(post.publishDate, 'yyyy-MM')}
|
||||
</div>
|
||||
{/* 发布日期 */}
|
||||
<Link
|
||||
className='text-sm'
|
||||
href={`/archive#${formatDateFmt(post?.publishDate, 'yyyy-MM')}`}
|
||||
passHref>
|
||||
{formatDateFmt(post?.publishDate, 'yyyy-MM')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,111 +1,101 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import PostItemCard from './PostItemCard'
|
||||
|
||||
const Swiper = ({ posts }) => {
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
const containerRef = useRef(null)
|
||||
const InertiaCarousel = ({ posts }) => {
|
||||
const carouselRef = 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 scrollStartLeft = useRef(0) // 记录拖拽开始时的滚动位置
|
||||
|
||||
// 处理鼠标和触摸开始事件
|
||||
const handleDragStart = e => {
|
||||
const x = e.touches ? e.touches[0].clientX : e.clientX
|
||||
touchStartPos.current = { x }
|
||||
isDragging.current = true
|
||||
scrollStartLeft.current = containerRef.current.scrollLeft
|
||||
|
||||
// 更新鼠标样式
|
||||
containerRef.current.style.cursor = 'grabbing'
|
||||
// 开始拖拽事件
|
||||
const startDrag = e => {
|
||||
e.preventDefault()
|
||||
setIsDragging(true)
|
||||
const startPosition = e.pageX || e.touches[0].pageX
|
||||
setStartX(startPosition - carouselRef.current.offsetLeft)
|
||||
setScrollLeft(carouselRef.current.scrollLeft)
|
||||
setLastX(startPosition) // 初始化上一次的位置
|
||||
cancelInertiaScroll() // 停止任何正在进行的惯性动画
|
||||
}
|
||||
|
||||
// 处理鼠标和触摸移动事件
|
||||
const handleDragMove = e => {
|
||||
if (!isDragging.current) return
|
||||
// 拖拽中事件
|
||||
const duringDrag = e => {
|
||||
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
|
||||
|
||||
// 根据拖动的距离更新滚动位置
|
||||
containerRef.current.scrollLeft = scrollStartLeft.current + deltaX
|
||||
// 计算当前速度
|
||||
const deltaX = currentPosition - lastX
|
||||
setVelocity(deltaX) // 更新速度
|
||||
setLastX(currentPosition) // 更新 lastX 为当前位置
|
||||
}
|
||||
|
||||
// 处理鼠标和触摸结束事件
|
||||
const handleDragEnd = () => {
|
||||
isDragging.current = false
|
||||
containerRef.current.style.cursor = 'grab'
|
||||
// 结束拖拽事件,启动惯性滚动
|
||||
const endDrag = () => {
|
||||
setIsDragging(false)
|
||||
startInertiaScroll(velocity) // 根据最终速度启动惯性滚动
|
||||
}
|
||||
|
||||
// 处理指示器点击事件
|
||||
const handleIndicatorClick = index => {
|
||||
setCurrentIndex(index)
|
||||
scrollToCard(index)
|
||||
// 惯性滚动函数
|
||||
const startInertiaScroll = initialVelocity => {
|
||||
let currentVelocity = initialVelocity
|
||||
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 container = containerRef.current
|
||||
if (!container) return
|
||||
const cardWidth = container.scrollWidth / posts.length
|
||||
container.scrollTo({
|
||||
left: index * cardWidth - cardWidth / 6, // 调整位置以居中
|
||||
behavior: 'smooth'
|
||||
})
|
||||
// 取消惯性滚动
|
||||
const cancelInertiaScroll = () => {
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => cancelInertiaScroll() // 清除动画
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='relative w-full mx-auto px-12 my-8'>
|
||||
<div
|
||||
className='absolute inset-y-0 left-0 w-12 z-10 cursor-pointer bg-black hover:opacity-20 opacity-10 duration-100'
|
||||
onClick={() =>
|
||||
handleIndicatorClick(
|
||||
currentIndex === 0 ? posts.length - 1 : currentIndex - 1
|
||||
)
|
||||
}></div>
|
||||
<div
|
||||
ref={carouselRef}
|
||||
className={`flex w-screen overflow-x-auto space-x-6 ${
|
||||
isDragging ? 'cursor-grabbing' : 'cursor-grab'
|
||||
}`}
|
||||
onMouseDown={startDrag}
|
||||
onMouseMove={duringDrag}
|
||||
onMouseUp={endDrag}
|
||||
onMouseLeave={endDrag}
|
||||
onTouchStart={startDrag}
|
||||
onTouchMove={duringDrag}
|
||||
onTouchEnd={endDrag}
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
|
||||
{/* Carousel items */}
|
||||
|
||||
<div
|
||||
className='absolute inset-y-0 right-0 w-12 z-10 cursor-pointer bg-black hover:opacity-20 opacity-10 duration-100'
|
||||
onClick={() =>
|
||||
handleIndicatorClick(
|
||||
currentIndex === posts.length - 1 ? 0 : currentIndex + 1
|
||||
)
|
||||
}></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
|
||||
<div className='min-w-[33vw]' />
|
||||
{posts &&
|
||||
posts?.map((post, index) => (
|
||||
<PostItemCard
|
||||
className='min-w-[33vw] flex items-center justify-center'
|
||||
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>
|
||||
post={post}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Swiper
|
||||
export default InertiaCarousel
|
||||
|
||||
@@ -65,7 +65,7 @@ const LayoutBase = props => {
|
||||
<ThemeGlobalPhoto.Provider
|
||||
value={{ searchModal, expandMenu, updateExpandMenu, collapseRef }}>
|
||||
<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`}>
|
||||
<Style />
|
||||
|
||||
@@ -133,7 +133,7 @@ const LayoutIndex = props => {
|
||||
*/
|
||||
const LayoutPostList = props => {
|
||||
return (
|
||||
<div className='max-w-[90rem] mx-auto'>
|
||||
<div className='mx-auto'>
|
||||
<SlotBar {...props} />
|
||||
{/* 滑动组件 */}
|
||||
<Swiper {...props} />
|
||||
@@ -480,15 +480,14 @@ const LayoutTagIndex = props => {
|
||||
}
|
||||
|
||||
export {
|
||||
Layout404,
|
||||
LayoutArchive,
|
||||
LayoutBase,
|
||||
LayoutCategoryIndex,
|
||||
LayoutIndex,
|
||||
LayoutPostList,
|
||||
LayoutSearch,
|
||||
LayoutSlug,
|
||||
LayoutTagIndex,
|
||||
CONFIG as THEME_CONFIG
|
||||
Layout404,
|
||||
LayoutArchive,
|
||||
LayoutBase,
|
||||
LayoutCategoryIndex,
|
||||
LayoutIndex,
|
||||
LayoutPostList,
|
||||
LayoutSearch,
|
||||
LayoutSlug,
|
||||
LayoutTagIndex,
|
||||
CONFIG as THEME_CONFIG
|
||||
}
|
||||
|
||||
|
||||
@@ -11,39 +11,17 @@ const Style = () => {
|
||||
.dark body {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.shadow-movie {
|
||||
box-shadow:
|
||||
0 26px 58px 0 rgba(0, 0, 0, 0.22),
|
||||
0 5px 14px 0 rgba(0, 0, 0, 0.18);
|
||||
// 毛玻璃背景色
|
||||
.bg-glassmorphism {
|
||||
background: hsla(0, 0%, 100%, 0.4);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
// 视频聚合走马灯
|
||||
.notion-carousel {
|
||||
width: 100%; /* 根据需要调整 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.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;
|
||||
.dark .bg-glassmorphism {
|
||||
background: hsla(0, 0%, 0%, 0.4);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
`}</style>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user