const siteHeader = document.getElementById('siteHeader') const mainNavbar = document.getElementById('mainNavbar') const navbarToggler = document.querySelector('.navbar-toggler') const siteLogoImg = document.getElementById('siteLogoImg') const navMode = siteHeader?.dataset?.navMode || 'inner' const isHomeNav = navMode === 'home' const bootstrapCollapse = mainNavbar && window.bootstrap ? new bootstrap.Collapse(mainNavbar, { toggle: false }) : null const setHeroHeaderTheme = (isDark) => { if (!siteHeader) return siteHeader.classList.remove('hero-theme-light', 'hero-theme-dark') siteHeader.classList.add(isDark ? 'hero-theme-dark' : 'hero-theme-light') if (siteLogoImg) { const lightLogo = siteLogoImg.getAttribute('data-logo-light') || '/assets/xtu/vendor/img/XTU-logo.png' const darkLogo = siteLogoImg.getAttribute('data-logo-dark') || '/assets/xtu/vendor/img/XTU-logo2.png' siteLogoImg.src = isDark ? darkLogo : lightLogo } } const syncHeroHeaderThemeFromSlide = () => { if (!siteHeader || siteHeader.classList.contains('scrolled') || siteHeader.classList.contains('menu-open') || siteHeader.classList.contains('header-search-open')) return const activeSlide = document.querySelector('#heroSwiper .swiper-slide-active[data-is-b], #heroSwiper .swiper-slide[data-swiper-slide-index][data-is-b].swiper-slide-active') || document.querySelector('#heroSwiper .swiper-slide[data-is-b]') const isB = activeSlide ? String(activeSlide.getAttribute('data-is-b') || '0') : '0' setHeroHeaderTheme(isB === '1') } const syncHeaderState = () => { const menuOpen = !!mainNavbar?.classList.contains('show') if (siteHeader) siteHeader.classList.toggle('menu-open', menuOpen) if (navbarToggler) navbarToggler.setAttribute('aria-expanded', menuOpen ? 'true' : 'false') } const onScroll = () => { if (!siteHeader || !isHomeNav || siteHeader.classList.contains('page-navbar-solid') || siteHeader.classList.contains('header-search-open')) return if (window.scrollY > 30) { siteHeader.classList.add('scrolled') setHeroHeaderTheme(true) } else if (!siteHeader.classList.contains('menu-open')) { siteHeader.classList.remove('scrolled') syncHeroHeaderThemeFromSlide() } } if (isHomeNav) { window.addEventListener('scroll', onScroll) onScroll() } else { siteHeader?.classList.add('scrolled') setHeroHeaderTheme(true) } mainNavbar?.addEventListener('show.bs.collapse', () => { siteHeader?.classList.add('menu-open', 'scrolled') setHeroHeaderTheme(true) document.body.classList.add('modal-open') syncHeaderState() }) mainNavbar?.addEventListener('hide.bs.collapse', () => { siteHeader?.classList.remove('menu-open') document.body.classList.remove('modal-open') syncHeaderState() onScroll() }) mainNavbar?.addEventListener('shown.bs.collapse', syncHeaderState) mainNavbar?.addEventListener('hidden.bs.collapse', syncHeaderState) mainNavbar?.querySelectorAll('.nav-link:not(.dropdown-toggle), .dropdown-item').forEach((link) => { link.addEventListener('click', () => { if (window.innerWidth < 992 && mainNavbar.classList.contains('show')) bootstrapCollapse?.hide() }) }) if (mainNavbar && window.bootstrap) { mainNavbar.querySelectorAll('.nav-item.dropdown').forEach((item) => { const toggle = item.querySelector(':scope > .dropdown-toggle') const menu = item.querySelector(':scope > .dropdown-menu') if (!toggle || !menu) return const dropdownInstance = bootstrap.Dropdown.getOrCreateInstance(toggle, { autoClose: true }) let hideTimer = null const clearHideTimer = () => { if (hideTimer) { clearTimeout(hideTimer) hideTimer = null } } const showDesktopDropdown = () => { if (window.innerWidth < 992 || siteHeader?.classList.contains('header-search-open')) return clearHideTimer() item.classList.add('show') toggle.classList.add('show') toggle.setAttribute('aria-expanded', 'true') menu.classList.add('show') dropdownInstance.show() } const hideDesktopDropdown = () => { if (window.innerWidth < 992) return clearHideTimer() item.classList.remove('show') toggle.classList.remove('show') toggle.setAttribute('aria-expanded', 'false') menu.classList.remove('show') dropdownInstance.hide() } const scheduleHideDesktopDropdown = () => { if (window.innerWidth < 992) return clearHideTimer() hideTimer = setTimeout(() => hideDesktopDropdown(), 120) } toggle.removeAttribute('data-bs-toggle') item.addEventListener('mouseenter', showDesktopDropdown) item.addEventListener('mouseleave', scheduleHideDesktopDropdown) menu.addEventListener('mouseenter', showDesktopDropdown) menu.addEventListener('mouseleave', scheduleHideDesktopDropdown) toggle.addEventListener('focus', showDesktopDropdown) item.addEventListener('focusout', () => { setTimeout(() => { if (!item.contains(document.activeElement)) scheduleHideDesktopDropdown() }, 0) }) toggle.addEventListener('click', (e) => { if (window.innerWidth >= 992) { const href = toggle.getAttribute('href') || '' if (!href || href === '#') e.preventDefault() } }) window.addEventListener('resize', () => { if (window.innerWidth < 992) { clearHideTimer() hideDesktopDropdown() toggle.setAttribute('data-bs-toggle', 'dropdown') } else { toggle.removeAttribute('data-bs-toggle') } }) }) } const openModal = (id) => { const modal = document.getElementById(id) if (!modal) return modal.classList.add('is-open') document.body.classList.add('modal-open') } const closeModal = (id) => { const modal = document.getElementById(id) if (!modal) return modal.classList.remove('is-open') if (!document.querySelector('.custom-modal.is-open') && !mainNavbar?.classList.contains('show')) document.body.classList.remove('modal-open') } document.getElementById('openLanguageModal')?.addEventListener('click', () => openModal('languageModal')) document.getElementById('openLanguageModalMobile')?.addEventListener('click', () => openModal('languageModal')) document.getElementById('openLanguageModalFooter')?.addEventListener('click', () => openModal('languageModal')) document.querySelectorAll('[data-close-modal]').forEach((btn) => btn.addEventListener('click', () => closeModal(btn.dataset.closeModal))) document.querySelectorAll('.custom-modal').forEach((modal) => { modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(modal.id) }) }) const headerInlineSearch = document.getElementById('headerInlineSearch') const openHeaderSearchBtn = document.getElementById('openHeaderSearch') const closeHeaderSearchBtn = document.getElementById('closeHeaderSearch') const headerInlineSearchInput = document.getElementById('headerInlineSearchInput') const headerInlineSearchForm = document.getElementById('headerInlineSearchForm') const openHeaderSearch = () => { if (!headerInlineSearch || window.innerWidth < 992) return siteHeader?.classList.add('header-search-open', 'scrolled') setHeroHeaderTheme(true) headerInlineSearch.classList.add('is-open') headerInlineSearch.setAttribute('aria-hidden', 'false') document.body.classList.add('modal-open') setTimeout(() => { headerInlineSearchInput?.focus() if (headerInlineSearchInput?.value) { headerInlineSearchInput.setSelectionRange(0, headerInlineSearchInput.value.length) } }, 60) } const closeHeaderSearch = () => { if (!headerInlineSearch) return siteHeader?.classList.remove('header-search-open') headerInlineSearch.classList.remove('is-open') headerInlineSearch.setAttribute('aria-hidden', 'true') headerInlineSearchInput?.blur() if (!mainNavbar?.classList.contains('show')) document.body.classList.remove('modal-open') if (isHomeNav) { onScroll() if (window.scrollY <= 30 && !mainNavbar?.classList.contains('show')) syncHeroHeaderThemeFromSlide() } } openHeaderSearchBtn?.addEventListener('click', (e) => { e.preventDefault() e.stopPropagation() openHeaderSearch() }) closeHeaderSearchBtn?.addEventListener('click', (e) => { e.preventDefault() e.stopPropagation() closeHeaderSearch() }) document.getElementById('openSearchModalMobile')?.addEventListener('click', () => { window.location.href = '/search' }) headerInlineSearchForm?.addEventListener('submit', (e) => { const keyword = (headerInlineSearchInput?.value || '').trim() if (!keyword) { e.preventDefault() headerInlineSearchInput?.focus() headerInlineSearchInput?.setCustomValidity(document.documentElement.lang === 'en' ? 'Please enter a keyword' : '请输入搜索关键词') headerInlineSearchInput?.reportValidity() return } headerInlineSearchInput?.setCustomValidity('') }) headerInlineSearchInput?.addEventListener('input', (e) => { e.target.setCustomValidity('') }) document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && headerInlineSearch?.classList.contains('is-open')) closeHeaderSearch() }) document.querySelectorAll('.footer-social-item').forEach((item) => { const closeAll = () => document.querySelectorAll('.footer-social-item.is-open').forEach((el) => el.classList.remove('is-open')) item.addEventListener('mouseenter', () => item.classList.add('is-open')) item.addEventListener('mouseleave', () => item.classList.remove('is-open')) item.addEventListener('click', (e) => { if (window.matchMedia('(hover: none)').matches) { e.preventDefault() const willOpen = !item.classList.contains('is-open') closeAll() if (willOpen) item.classList.add('is-open') } }) item.addEventListener('focusin', () => item.classList.add('is-open')) item.addEventListener('focusout', () => item.classList.remove('is-open')) document.addEventListener('click', (e) => { if (!item.contains(e.target)) item.classList.remove('is-open') }) }) const resetSlideVideos = (selector) => { document.querySelectorAll(selector).forEach((video) => { try { video.pause(); video.currentTime = 0 } catch {} video.classList.remove('is-ready') }) } const playSlideVideo = (slide) => { if (!slide) return const video = slide.querySelector('.slide-video') if (!video) return const showVideo = () => video.classList.add('is-ready') if (video.readyState >= 2) { showVideo() } else { video.addEventListener('loadeddata', showVideo, { once: true }) video.addEventListener('canplay', showVideo, { once: true }) } video.play().then(showVideo).catch(() => {}) } document.querySelectorAll('.slide-media-layer, .card-media-layer').forEach((wrap) => { wrap.addEventListener('click', () => { playSlideVideo(wrap.closest('.swiper-slide') || wrap) }) }) if (document.querySelector('#heroSwiper') && window.Swiper) { const heroSlides = Array.from(document.querySelectorAll('#heroSwiper .swiper-slide[data-bullet-title]')) const heroBulletTitles = heroSlides.map((slide) => slide.getAttribute('data-bullet-title') || '') new Swiper('#heroSwiper', { loop: true, speed: 900, effect: 'fade', fadeEffect: { crossFade: true }, autoplay: { delay: 5000, disableOnInteraction: false }, navigation: { nextEl: '.hero-next', prevEl: '.hero-prev' }, pagination: { el: '.hero-pagination', clickable: true, renderBullet: function (index, className) { return `${heroBulletTitles[index] || ''}` }, }, on: { init(swiper) { syncHeroHeaderThemeFromSlide() playSlideVideo(swiper.slides[swiper.activeIndex]) }, slideChangeTransitionStart: () => { resetSlideVideos('#heroSwiper .slide-video') syncHeroHeaderThemeFromSlide() }, slideChangeTransitionEnd(swiper) { playSlideVideo(swiper.slides[swiper.activeIndex]) } }, }) syncHeroHeaderThemeFromSlide() } if (document.querySelector('#showcaseSwiper') && window.Swiper) { const showcaseEl = document.querySelector('#showcaseSwiper') const showcaseWrapper = showcaseEl ? showcaseEl.querySelector('.swiper-wrapper') : null if (showcaseWrapper) { const originalSlides = Array.from(showcaseWrapper.querySelectorAll('.swiper-slide')) if (originalSlides.length > 0 && originalSlides.length < 4) { let i = 0 while (showcaseWrapper.querySelectorAll('.swiper-slide').length < 4) { const clone = originalSlides[i % originalSlides.length].cloneNode(true) clone.classList.add('showcase-card-fill-js') showcaseWrapper.appendChild(clone) i++ } } } new Swiper('#showcaseSwiper', { loop: false, rewind: true, speed: 800, centeredSlides: false, watchOverflow: false, slidesPerView: 1.08, spaceBetween: 12, autoplay: { delay: 4500, disableOnInteraction: false, pauseOnMouseEnter: true }, navigation: { nextEl: '.showcase-next', prevEl: '.showcase-prev' }, pagination: { el: '.showcase-pagination', clickable: true }, breakpoints: { 768: { slidesPerView: 1.3, spaceBetween: 12 }, 992: { slidesPerView: 2, spaceBetween: 12 } }, on: { init(swiper) { const bullets = swiper.pagination && swiper.pagination.bullets ? swiper.pagination.bullets : [] bullets.forEach((bullet, index) => { bullet.style.setProperty('--showcase-progress-delay', index === swiper.activeIndex ? '0ms' : '0ms') }) }, slideChangeTransitionStart(swiper) { resetSlideVideos('#showcaseSwiper .slide-video') const bullets = swiper.pagination && swiper.pagination.bullets ? swiper.pagination.bullets : [] bullets.forEach((bullet) => { bullet.style.animation = 'none' void bullet.offsetWidth bullet.style.animation = '' }) } } }) } const backToTopBtn = document.getElementById('backToTopBtn') const backToTopShowOffset = 320 const feedbackFloatForm = document.getElementById('feedbackFloatForm') const feedbackFloatRatingInput = document.getElementById('feedbackFloatRating') const feedbackFloatSubmit = document.getElementById('feedbackFloatSubmit') const feedbackFloatOptions = document.querySelectorAll('.feedback-float-option') const feedbackFloatPrompt = document.querySelector('.feedback-float-rating-block h3') const feedbackFloatMessage = document.getElementById('feedbackFloatMessage') const feedbackSourcePageInput = feedbackFloatForm?.querySelector('input[name="source_page"]') const feedbackStars = Array.from(document.querySelectorAll('.feedback-star')) const feedbackPromptMap = { website: '请对“官网体验”进行评价。', service: '请对“服务体验”进行评价。', product: '请对“产品体验”进行评价。' } const syncFeedbackPrompt = (value) => { const key = value || 'website' if (feedbackFloatPrompt && feedbackPromptMap[key]) feedbackFloatPrompt.textContent = feedbackPromptMap[key] } const setFeedbackMessage = (text, type = '') => { if (!feedbackFloatMessage) return feedbackFloatMessage.textContent = text || '' feedbackFloatMessage.className = 'feedback-float-message' + (type ? ` is-${type}` : '') } const syncBackToTopVisibility = () => { if (!backToTopBtn) return const isVisible = window.scrollY > backToTopShowOffset backToTopBtn.classList.toggle('is-visible', isVisible) } backToTopBtn?.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }) }) window.addEventListener('scroll', syncBackToTopVisibility, { passive: true }) syncBackToTopVisibility() const setFeedbackRating = (value) => { const rating = Number(value) || 0 if (feedbackFloatRatingInput) feedbackFloatRatingInput.value = String(rating) feedbackStars.forEach((star) => { const starValue = Number(star.dataset.value || 0) star.classList.toggle('is-active', starValue <= rating) }) if (feedbackFloatSubmit) feedbackFloatSubmit.disabled = rating < 1 } feedbackStars.forEach((star) => { star.addEventListener('click', () => setFeedbackRating(star.dataset.value || 0)) }) feedbackFloatOptions.forEach((item) => { const input = item.querySelector('input[type="radio"]') input?.addEventListener('change', () => { feedbackFloatOptions.forEach((node) => node.classList.remove('is-active')) item.classList.add('is-active') const value = input.value || 'website' syncFeedbackPrompt(value) setFeedbackRating(0) setFeedbackMessage('') }) }) if (feedbackSourcePageInput) feedbackSourcePageInput.value = window.location.href syncFeedbackPrompt(document.querySelector('.feedback-float-option input[type="radio"]:checked')?.value || 'website') feedbackFloatForm?.addEventListener('submit', async (e) => { e.preventDefault() const rating = Number(feedbackFloatRatingInput?.value || 0) || 0 if (rating < 1) return const submitUrl = feedbackFloatForm.dataset.submitUrl || '' if (!submitUrl) { setFeedbackMessage('提交地址缺失', 'error') return } const originalText = feedbackFloatSubmit?.textContent || '提交' if (feedbackFloatSubmit) { feedbackFloatSubmit.disabled = true feedbackFloatSubmit.textContent = '提交中...' } setFeedbackMessage('') try { const body = new URLSearchParams(new FormData(feedbackFloatForm)) const res = await fetch(submitUrl, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: body.toString() }) const data = await res.json() if (data.code === 1) { setFeedbackMessage(data.msg || '提交成功,感谢你的评价', 'success') feedbackFloatForm.reset() feedbackFloatOptions.forEach((node, index) => node.classList.toggle('is-active', index === 0)) syncFeedbackPrompt('website') setFeedbackRating(0) } else { setFeedbackMessage(data.msg || '提交失败,请稍后再试', 'error') if (feedbackFloatSubmit) feedbackFloatSubmit.disabled = rating < 1 } } catch (err) { setFeedbackMessage('提交失败,请稍后再试', 'error') if (feedbackFloatSubmit) feedbackFloatSubmit.disabled = rating < 1 } finally { if (feedbackFloatSubmit) feedbackFloatSubmit.textContent = originalText } })