This commit is contained in:
2026-07-14 20:19:21 +00:00
parent db9370f916
commit 573a4ae915
52 changed files with 2955 additions and 2366 deletions

View File

@@ -1,6 +1,6 @@
import { useNavigate } from 'react-router-dom'
import { useEffect } from 'react'
import { SPA_ROUTES, POST_ID_TO_SLUG } from '../config'
import { SPA_ROUTES } from '../config'
export function useNavFix(ref) {
const navigate = useNavigate()
@@ -34,17 +34,6 @@ export function useNavFix(ref) {
// Normalize: remove leading /, handle relative paths
let route = href.replace(/^\//, '')
// Handle ?p= service links → SPA route
const serviceMatch = route.match(/service\?p=(\d+)/)
if (serviceMatch) {
const slug = POST_ID_TO_SLUG[serviceMatch[1]]
if (slug) {
navigate('/service/' + slug)
} else {
navigate('/service')
}
return
}
// Handle ../ x/index.html relative paths like ../contact/index.html
route = route.replace(/\.\.\//g, '')

View File

@@ -15,8 +15,7 @@ export default function useSwiper(containerRef) {
instances.current.forEach(s => s.destroy(true, true))
instances.current = []
// Init all .th-slider elements
container.querySelectorAll('.th-slider').forEach(el => {
const initSlider = (el) => {
try {
const userOptions = JSON.parse(el.dataset.sliderOptions || '{}')
@@ -30,6 +29,13 @@ export default function useSwiper(containerRef) {
const hasNumberPagi = el.querySelector('.slider-pagination.pagi-number')
const paginationType = userOptions.paginationType || 'bullets'
// Keys the hook explicitly handles — everything else is forwarded to Swiper
const knownKeys = [
'slidesPerView', 'spaceBetween', 'loop', 'speed', 'autoplay',
'effect', 'centeredSlides', 'slidesPerGroup', 'breakpoints',
'paginationType', 'pagination', 'navigation', 'on', 'modules'
]
const swiper = new Swiper(el, {
modules: [Navigation, Pagination, Autoplay, EffectFade],
slidesPerView: userOptions.slidesPerView || 1,
@@ -41,6 +47,8 @@ export default function useSwiper(containerRef) {
centeredSlides: userOptions.centeredSlides || false,
slidesPerGroup: userOptions.slidesPerGroup || 1,
breakpoints: userOptions.breakpoints || {},
observer: true,
observeParents: true,
navigation: {
nextEl: el.querySelector('.slider-next'),
prevEl: el.querySelector('.slider-prev'),
@@ -70,14 +78,42 @@ export default function useSwiper(containerRef) {
},
...(userOptions.on || {}),
},
// Forward unknown userOptions to Swiper (e.g. centeredSlidesBounds, freeMode, mousewheel, grabCursor)
...Object.fromEntries(
Object.entries(userOptions).filter(([key]) => !knownKeys.includes(key))
),
})
instances.current.push(swiper)
// Fade sliders: trigger the CSS ::after overlay fade-out animation
if (userOptions.effect === 'fade') {
el.classList.add('fade-ani')
}
} catch (e) {
console.warn('useSwiper: failed to init', el.id || el.className, e)
}
}
// Init all visible .th-slider elements (skip hidden tab panes)
container.querySelectorAll('.th-slider').forEach(el => {
const tabPane = el.closest('.tab-pane')
if (tabPane && !tabPane.classList.contains('active')) return
initSlider(el)
})
// Listen for Bootstrap tab shown events to init deferred sliders
const handleTabShown = (e) => {
const targetId = e.target?.getAttribute('data-bs-target')
if (!targetId) return
const pane = document.querySelector(targetId)
if (!pane) return
pane.querySelectorAll('.th-slider').forEach(el => {
if (!el.swiper) requestAnimationFrame(() => initSlider(el))
})
}
document.addEventListener('shown.bs.tab', handleTabShown)
// External controls: [data-slider-prev] and [data-slider-next]
const handleClick = (e) => {
const btn = e.target.closest('[data-slider-prev], [data-slider-next]')
@@ -128,6 +164,7 @@ export default function useSwiper(containerRef) {
instances.current.forEach(s => s.destroy(true, true))
instances.current = []
container.removeEventListener('click', handleClick)
document.removeEventListener('shown.bs.tab', handleTabShown)
}
}, [containerRef, pathname])
}