PageSourceSearch

https://sh-aquarium.com/common/js/public.js

js sh-aquarium.com collected 2026-10-02 10:50:08 UTC 71,804 bytes, 1,857 lines download raw bytes

1// 断点配置
2let whirBreakpoints = { mobile: 750, tablet: 1024, desktopContent: 1600, desktop: 1920 };
3// 移动端适配配置 默认关闭
4let whirMobileConfig = {
5    enabled: false, // 是否启用移动端适配
6    designWidth: 375, // 基准设计稿宽度
7    designFontSize: 100, // 基准fontSize
8    breakpoints: [ // 断点配置数组
9        { width: 375 },
10        { width: 414 },
11        { width: 639 },
12        { width: 749 }
13    ]
14};
15
16// Mac设备字体大小适配
17function initMacFontSize() {
18    function isMac() {
19        return /Macintosh|MacIntel|MacPPC|Mac68K/.test(navigator.userAgent);
20    }
21
22    if (isMac()) {
23        $('html').addClass('isMac');
24        function updateFontSize() {
25            // 获取视口宽度
26            var viewportWidth = window.innerWidth;
27            if (viewportWidth < whirBreakpoints.desktopContent) {
28                // 根据公式计算字体大小
29                var fontSize = (100 / whirBreakpoints.desktopContent) * viewportWidth;
30                // 设置给 html 元素
31                $('html').css('font-size', fontSize + 'px');
32            } else {
33                $('html').removeAttr("style")
34            }
35        }
36
37        // 初始化字体大小
38        updateFontSize();
39
40        // 监听窗口大小变化,包括缩放
41        window.addEventListener('resize', updateFontSize);
42    }
43}
44
45// 移动端适配
46(function (win, doc) {
47    // 如果未启用移动端适配,执行Mac设备字体大小适配
48    if (!whirMobileConfig.enabled) {
49        initMacFontSize();
50        return;
51    }
52
53    var docEl = doc.documentElement;
54
55    // 使用配置中的值
56    var designWidth = whirMobileConfig.designWidth;
57    var designFontSize = whirMobileConfig.designFontSize;
58    var breakpoints = whirMobileConfig.breakpoints;
59
60    // 断点配置数组,只需要配置宽度,base值会自动计算
61    var breakpoints = [
62        { width: 375 },
63        { width: 414 },
64        { width: 639 },
65        { width: 749 }
66    ];
67
68    // 计算每个断点的基准值
69    function calculateBaseValues() {
70        for (var i = 0; i < breakpoints.length; i++) {
71            // 基准值计算公式:(断点宽度/设计稿宽度) * 设计稿基准fontSize * 设计系数
72            // 设计系数可以根据需要调整,这里使用0.9~1.1的系数使得不同断点有细微差别
73            var factor = 0.9 + (i / (breakpoints.length - 1)) * 0.2;
74            breakpoints[i].base = (breakpoints[i].width / designWidth) * designFontSize * factor;
75        }
76    }
77
78    // 初始化计算基准值
79    calculateBaseValues();
80
81    function setRemUnit() {
82        var clientWidth = docEl.clientWidth;
83        var clientHeight = docEl.clientHeight;
84        var rem = designFontSize; // 默认基准值
85
86        // 只在移动端(小于750px)时进行比例计算
87        if (clientWidth < whirBreakpoints.mobile) {
88            // 处理屏幕旋转的情况
89            if (clientWidth > clientHeight) {
90                // 横屏模式
91                // 使用设备高度和宽度的比例来计算基准值
92                var ratio = clientHeight / clientWidth;
93                // 根据比例动态调整缩放系数
94                var scaleFactor = ratio > 0.5 ? 0.8 : 1;
95                // 使用较小的值作为基准,避免字体过大
96                var baseWidth = Math.min(clientWidth, whirBreakpoints.mobile);
97                rem = (baseWidth / designWidth) * designFontSize * scaleFactor;
98            } else {
99                // 竖屏模式
100                // 查找合适的断点
101                for (var i = 0; i < breakpoints.length; i++) {
102                    if (clientWidth <= breakpoints[i].width) {
103                        rem = (clientWidth / breakpoints[i].width) * breakpoints[i].base;
104                        break;
105                    }
106                }
107            }
108        } else {
109            // 桌面端考虑分辨率缩放
110            if (clientWidth > whirBreakpoints.desktopContent && clientWidth <= whirBreakpoints.desktop) {
111                rem = 100;
112            } else if (clientWidth > whirBreakpoints.desktop) {
113                // 超过1920px时,继续按比例增长
114                rem = (clientWidth / whirBreakpoints.desktop) * 100;
115            } else {
116                rem = designFontSize / whirBreakpoints.desktopContent * clientWidth;
117            }
118        }
119
120        // 限制最大和最小字体大小
121        rem = Math.min(Math.max(rem, 50), 400); // 调整最大字体限制为200
122        docEl.style.fontSize = rem + 'px';
123    }
124
125    // 监听窗口大小变化
126    win.addEventListener('resize', setRemUnit);
127    // 监听页面显示事件
128    win.addEventListener('pageshow', function (e) {
129        if (e.persisted) {
130            setRemUnit();
131        }
132    });
133    // 监听缩放变化
134    win.addEventListener('zoom', setRemUnit);
135
136    // 监听屏幕旋转事件(兼容性处理)
137    var orientation = 'portrait';
138    var orientationTimer = null;
139
140    // 检测设备方向变化
141    function handleOrientation() {
142        var currentOrientation = window.innerWidth > window.innerHeight ? 'landscape' : 'portrait';
143        if (currentOrientation !== orientation) {
144            orientation = currentOrientation;
145            // 清除之前的定时器
146            if (orientationTimer) {
147                clearTimeout(orientationTimer);
148            }
149            // 设置新的定时器,确保旋转完成后再计算
150            orientationTimer = setTimeout(setRemUnit, 300); // 增加延时确保旋转完成
151        }
152    }
153
154    // 使用多种方式监听屏幕旋转
155    if (window.orientation !== undefined) {
156        // iOS设备
157        win.addEventListener('orientationchange', handleOrientation);
158    }
159
160    // 监听resize事件来处理屏幕旋转
161    var resizeTimer = null;
162    win.addEventListener('resize', function () {
163        if (resizeTimer) {
164            clearTimeout(resizeTimer);
165        }
166        resizeTimer = setTimeout(handleOrientation, 300); // 增加延时确保旋转完成
167    });
168
169    // 初始化
170    setRemUnit();
171})(window, document);
172//移动端适配  END
173/**
174 * 整站调用方法
175 */
176const lenis = window.lenis = new Lenis({
177    autoRaf: true
178});
179
180let whirPublic = {};
181var isDark = 0; // 默认值,不是1或2
182whirPublic.config = {
183    // 为所有 data-effect 标签添加进入视窗动效
184    initDataEffectAnimations: function () {
185        const dataEffectElements = document.querySelectorAll('[data-effect]');
186
187        if (dataEffectElements.length === 0) return;
188
189        const observer = new IntersectionObserver((entries) => {
190            entries.forEach(entry => {
191                const element = entry.target;
192                const effectClass = element.getAttribute('data-effect');
193
194                if (effectClass) {
195                    if (entry.isIntersecting) {
196                        // 清除可能存在的隐藏定时器
197                        if (element._hideTimer) {
198                            clearTimeout(element._hideTimer);
199                            element._hideTimer = null;
200                        }
201
202                        // 元素进入视窗,添加动画样式类
203                        element.classList.add('in-view', effectClass);
204
205                        // 获取data-delay属性并设置动画延迟
206                        const delay = element.getAttribute('data-delay');
207                        if (delay) {
208                            element.style.animationDelay = delay;
209                            element.style.webkitAnimationDelay = delay;
210                        }
211                    } else {
212                        // 元素离开视窗,添加延迟再移除类,避免突然隐藏
213                        if (element._hideTimer) {
214                            clearTimeout(element._hideTimer);
215                        }
216
217                        element._hideTimer = setTimeout(() => {
218                            // 移除动画样式类和延迟样式
219                            element.classList.remove('in-view', effectClass);
220                            element.style.animationDelay = '';
221                            element.style.webkitAnimationDelay = '';
222                            element._hideTimer = null;
223                        }, 300); // 300ms延迟,可根据需要调整
224                    }
225                }
226            });
227        }, {
228            threshold: 0.1, // 当元素10%进入视窗时触发
229            rootMargin: '0px 0px -50px 0px' // 底部增加50px缓冲,让元素离开视窗更多才会隐藏
230        });
231
232        // 开始观察所有 data-effect 元素
233        dataEffectElements.forEach(element => {
234            observer.observe(element);
235        });
236    },
237    backTop: function () {
238        // 监听滚动事件
239        lenis.on('scroll', (e) => {
240            const scrollY = e.scroll;
241            if (scrollY > window.innerHeight) {
242                $('.gettop').addClass('show');
243            } else {
244                $('.gettop').removeClass('show');
245            }
246        });
247        //返回顶部
248        $('.gettop').click(function () {
249            lenis.scrollTo(0, { duration: 0.8 });
250        });
251    },
252    top: function () {
253        $("#m" + m).addClass('aon');
254        // header样式切换
255        function setHeaderStyle(isScroll, isHover) {
256            // 清除所有相关样式类
257            $("header").removeClass('has-whitebg  fixed');
258
259            if (isDark === 1) {
260                // isDark=1时,添加has-whitebg
261                $("header").addClass('has-whitebg');
262            } else if (isDark === 2) {
263                if (isHover) {
264                    // isDark=2且鼠标悬停时,添加has-whitebg
265                    $("header").addClass('has-whitebg');
266                }
267            }
268
269            // 滚动时添加fixed类
270            if (isScroll) {
271                $("header").addClass('fixed');
272            }
273        }
274
275        // 初始化
276        setHeaderStyle(false, false);
277
278        // 滚动方向检测变量
279        var lastScrollTop = 0;
280        var scrollThreshold = 5; // 滚动阈值,避免微小滚动触发
281
282        lenis.on('scroll', (e) => {
283            const scrollY = e.scroll;
284
285            // 检测滚动方向
286            if (Math.abs(scrollY - lastScrollTop) > scrollThreshold) {
287                if (scrollY > lastScrollTop) {
288                    // 向下滚动
289                    scrollDirection = 'down';
290                    $("header").addClass('up');
291                } else {
292                    // 向上滚动
293                    scrollDirection = 'up';
294                    $("header").removeClass('up');
295                }
296                lastScrollTop = scrollY;
297            }
298
299            setHeaderStyle(scrollY > 0, false);
300        });
301
302        $("header").hover(
303            function () {
304                // 鼠标进入header
305                if (isDark === 1) {
306                    // isDark=1时无需处理
307                    return;
308                } else if (isDark === 0) {
309                    // isDark=0时,添加has-whitebg
310                    $("header").addClass('has-whitebg');
311                }
312            },
313            function () {
314                // 鼠标离开header
315                if (isDark === 1) {
316                    // isDark=1时无需处理
317                    return;
318                } else if (isDark === 0) {
319                    // isDark=0时,去除has-whitebg
320                    $("header").removeClass('has-whitebg');
321                }
322            }
323        );
324
325        //打开下拉
326        $(".main-nav li").each(function () {
327            var num = $(this).find(".nav-sub").find("dl dd").length;
328            if (num > 0) {
329                $(this).addClass("has-sub");
330                $(this).find("span").append('<i class="wap-op"></i>');
331            }
332            $(this).hover(function () {
333                var $this = $(this);
334                $this.addClass('aon').siblings().removeClass('aon');
335                clearTimeout($this.data('hoverTimer'));
336                $this.data('hoverTimer', setTimeout(function () {
337                    $this.addClass("show").siblings().removeClass("show");
338                }, 300));
339            }, function () {
340                var $this = $(this);
341                $this.removeClass('aon');
342                $("#m" + m).addClass('aon')
343                clearTimeout($this.data('hoverTimer'));
344                $this.data('hoverTimer', setTimeout(function () {
345                    $this.removeClass("show");
346                }, 300));
347            });
348        });
349        $(".wap-op").click(function () {
350            $(this).parents("li").toggleClass("wap-show");
351            $(this).parents("li").siblings().removeClass("wap-show");
352        });
353
354        $(".main-nav").mouseleave(function () {
355            $(this).find("li").removeClass("show");
356        });
357        //打开语言
358        whirPublic.open.hoverCss('.top-lang', 'active');
359        whirPublic.open.hoverCss('.follow-us li', 'active');
360        //打开搜索
361        whirPublic.open.clickOpen('.open-search', 'body', 'search-show', '.top-search');
362        //移动端菜单
363        whirPublic.open.clickOpen('.open-menu', 'body', 'menu-show', '.main-nav');
364    },
365    toNoPic: function () {
366        $.each($("img"), function (i, n) {
367            $(n).on('error', function () {
368                n.src = noPicPath + 'no-pic.svg';
369            });
370            n.src = n.src;
371        });
372    },
373    bottom: function () {
374        //顶部
375        whirPublic.config.top();
376
377        $(".f-nav .navs .scroll-auto").each(function () {
378            let fddn = $(this).find("dd").length
379            if (fddn > 7) {
380                $(this).addClass("has-scroll");
381            }
382        })
383        //判断无图
384        whirPublic.config.toNoPic();
385        //返回顶部
386        whirPublic.config.backTop();
387
388        //禁止移动端Safari浏览器缩放
389        let userAgent = navigator.userAgent;
390        if (userAgent.includes("Safari")) {
391            document.addEventListener('gesturestart', function (event) {
392                event.preventDefault()
393            })
394        }
395        //滚动动画&数字滚动
396        window.scrollAnimate = new ScrollAnimate();
397        //播放视频
398        whirPublic.open.videoOpen();
399        //客服拖拽
400        whirPublic.config.dragOpen('.online-kf');
401        //客服iframe
402        whirPublic.config.iframeOpen();
403        //a标签右键禁止
404        if ($(window).width() > 1025) {
405            $(".follow-us li a").on("contextmenu", function (e) {
406                e.preventDefault();
407            });
408        }
409    },
410    // 原生JS实现拖拽功能(不依赖第三方库)
411    dragOpen: function (selector) {
412        const elements = document.querySelectorAll(selector);
413        if (elements.length === 0) return;
414
415        elements.forEach(el => {
416            const $el = $(el);
417            const storageKey = 'kf_position_' + el.id;
418            let isDragging = false;
419            let hasMoved = false; // 标记是否发生了移动
420            let startX, startY, initialX, initialY;
421
422            // 从localStorage恢复位置
423            try {
424                const savedPos = localStorage.getItem(storageKey);
425                if (savedPos) {
426                    const pos = JSON.parse(savedPos);
427                    el.style.left = pos.x + 'px';
428                    el.style.top = pos.y + 'px';
429                }
430            } catch (e) {
431                console.warn('Failed to restore position:', e);
432            }
433
434            // 阻止a标签拖拽后的跳转
435            const preventJump = (e) => {
436                if (hasMoved) {
437                    e.preventDefault();
438					if($(window).width()>=1025){
439                      e.stopPropagation();
440					}
441                }
442                // 300ms后移除阻止,恢复正常点击功能
443                setTimeout(() => {
444                    hasMoved = false;
445                }, 1000);
446            };
447
448            // 鼠标/触摸开始
449            const onStart = (e) => {
450				if($(window).width()>=1025){
451                  e.preventDefault();
452				}
453                isDragging = true;
454                hasMoved = false; // 重置移动标记
455
456                // 获取初始位置
457                const style = window.getComputedStyle(el);
458                initialX = parseInt(style.left) || 0;
459                initialY = parseInt(style.top) || 0;
460
461                // 获取鼠标/触摸位置
462                const point = e.touches ? e.touches[0] : e;
463                startX = point.clientX - initialX;
464                startY = point.clientY - initialY;
465
466                // 添加拖拽状态样式
467                $el.addClass('dragging').css('opacity', '0.8');
468
469                // 绑定移动和结束事件
470                document.addEventListener('mousemove', onMove);
471                document.addEventListener('mouseup', onEnd);
472                document.addEventListener('touchmove', onMove);
473                document.addEventListener('touchend', onEnd);
474            };
475
476            // 鼠标/触摸移动
477            const onMove = (e) => {
478                if (!isDragging) return;
479
480                const point = e.touches ? e.touches[0] : e;
481
482                // 计算新位置
483                let newX = point.clientX - startX;
484                let newY = point.clientY - startY;
485
486                // 检测是否发生了移动(超过5px视为移动)
487                const moveDistance = Math.abs(newX - initialX) + Math.abs(newY - initialY);
488                if (moveDistance > 5) {
489                    hasMoved = true;
490                }
491
492                // 边界检测(防止拖出屏幕)
493                const rect = el.getBoundingClientRect();
494                const maxX = window.innerWidth - rect.width - 10;
495                const maxY = window.innerHeight - rect.height - 10;
496
497                newX = Math.max(10, Math.min(newX, maxX));
498                newY = Math.max(10, Math.min(newY, maxY));
499
500                // 更新位置
501                el.style.left = newX + 'px';
502                el.style.top = newY + 'px';
503            };
504
505            // 鼠标/触摸结束
506            const onEnd = () => {
507                isDragging = false;
508                $el.removeClass('dragging').css('opacity', '');
509
510                // 解绑事件
511                document.removeEventListener('mousemove', onMove);
512                document.removeEventListener('mouseup', onEnd);
513                document.removeEventListener('touchmove', onMove);
514                document.removeEventListener('touchend', onEnd);
515
516                // 保存位置到localStorage
517                try {
518                    const style = window.getComputedStyle(el);
519                    localStorage.setItem(storageKey, JSON.stringify({
520                        x: parseInt(style.left) || 0,
521                        y: parseInt(style.top) || 0
522                    }));
523                } catch (e) {
524                    console.warn('Failed to save position:', e);
525                }
526            };
527
528            // 绑定开始事件
529            el.addEventListener('mousedown', onStart);
530            el.addEventListener('touchstart', onStart);
531
532            // 绑定click事件阻止跳转(针对a标签)
533            if (el.tagName === 'A') {
534                el.addEventListener('click', preventJump);
535            }
536
537        });
538    },
539    iframeOpen:function(){
540        $('.online-kf').click(function(){
541            let url=$(this).data('iframe');
542           $('body').append('<div class="kf-iframe"><a href="javascript:void(0);" class="close"></a><iframe src="'+url+'" frameborder="0"></iframe></div>');
543           $('.kf-iframe .close').click(function(){
544            $('.kf-iframe').remove();
545           });
546        })
547    },
548    getWeather(API_KEY, city, lang = 'zh-Hans') {
549        // 心知天气实时天气接口
550        const url = `https://api.seniverse.com/v3/weather/now.json?key=${API_KEY}&location=${city}&language=${lang}&unit=c`;
551
552        fetch(url)
553            .then(res => res.json())
554            .then(data => {
555                const result = data.results[0];
556                const now = result.now;
557
558                $("#weather").html(`<img src="${noPicPath}weather/${now.code}@1x.png">${now.temperature}℃<span>${now.text}</span>`)
559            })
560            .catch(err => {
561                console.error('天气获取失败:', err);
562            });
563    },
564    home: function () {
565        //banner 轮播
566        whirPublic.video.banner('.home-ban');
567        //主题展区
568        whirPublic.config.themeShow();
569        //本期明星
570        new Swiper('.home-star .swiper', {
571            slidesPerView: 1,
572            spaceBetween: 10,
573            loop: true,
574            speed: 500,
575            autoplay: {
576                delay: 4000,
577                disableOnInteraction: false
578            },
579            breakpoints: {
580                1024: {
581                    slidesPerView: 2,
582                    loopAdditionalSlides: 2,
583                    spaceBetween: 36,
584                }
585            }
586        })
587        //深海效果
588
589        if ($(".home-body").length != 0) {
590            $(function ($) {
591                $(window).on("resize", function (e) {
592                    var _fh = $("footer").outerHeight();
593                    $(".home-body").css("--footer-height", _fh);
594                }).trigger("resize");
595            });
596        }
597        $(document).ready(function () {
598            try {
599                $('.home-mygreen .bg').ripples({
600                    perturbance: 0.01,
601                    width: 300,
602                    height: 300
603                });
604            }
605            catch (e) {
606                $('.home-mygreen .error').show().text(e);
607            }
608        });
609        //初始化所有data-effect标签的动效
610        whirPublic.config.initDataEffectAnimations();
611        //喂食时间横向滚动
612        whirPublic.config.homefeedTimesB();
613    },
614    themeShow: function () {
615        $("#swiper-txt .swiper-slide").each(function (e) {
616            var ind = e + 1;
617            if (ind <= 9) {
618                ind = "0" + ind
619            }
620            $(this).find(".name").before("<div class='num'>" + ind + "</div>")
621        });
622
623        let txtSwiper = new Swiper('#swiper-txt', {
624            effect: 'fade',
625            fadeEffect: {
626                crossFade: true
627            }
628        })
629        let thumbsSwiper = new Swiper('#swiper-thumbs .swiper', {
630            slidesPerView: 5,
631            loopAdditionalSlides: 0,
632            spaceBetween: 10,
633            loop: false,
634            pagination: {
635                el: '.pages-process',
636                type: 'progressbar'
637            },
638            breakpoints: {
639                768: {
640                    slidesPerView: 4,
641                    loopAdditionalSlides: 4,
642                    spaceBetween: 16,
643                   loop: true,
644                }
645            }
646        })
647        $('#swiper-thumbs .swiper-slide .area').click(function () { 
648            if($(window).width() > 1025){
649                 thumbsSwiper.slideToLoop($(this).parent(".swiper-slide").attr('data-swiper-slide-index'), 1000, false);
650            }else{
651                thumbsSwiper.slideTo($(this).parent(".swiper-slide").index(), 1000, false);
652            }
653        });
654
655        let bigSwiper = new Swiper('.home-theme .swiper-big .swiper', {
656            // loop: true,
657            effect: 'fade',
658            speed: 1000,
659            thumbs: {
660                swiper: thumbsSwiper,
661            },
662            fadeEffect: {
663                crossFade: true
664            },            
665              controller: {
666                    control: txtSwiper,
667                },
668        })
669
670
671    },
672    homefeedTimes: function () {
673
674        whirPublic.config.pageFeedTime();
675        // 使用GSAP和ScrollTrigger实现滚动时的pin和横向滚动效果
676        if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
677            const container = $('.feed-times');
678            const content = container.find('.item');
679
680            // 确保有内容才执行
681            if (content.length > 0) {
682                // 计算总宽度
683                let totalWidth = 0;
684                content.each(function () {
685                    totalWidth += $(this).outerWidth();
686                });
687
688                // 设置容器宽度
689                container.css('width', totalWidth + 'px');
690
691                // 存储trigger的引用
692                let triggerInstance;
693
694                // 创建ScrollTrigger动画
695                gsap.to(container, {
696                    x: () => -(totalWidth - window.innerWidth),
697                    ease: 'none',
698                    scrollTrigger: {
699                        trigger: '.feed-times',
700                        pin: true,
701                        scrub: 0.05, // 进一步减小scrub值以提高响应速度
702                        end: () => `+=${totalWidth - window.innerWidth}`,
703                        invalidateOnRefresh: true,
704                        // 存储trigger实例
705                        onRefresh: self => {
706                            triggerInstance = self;
707                            // 初始化位置
708                            if (lenis) {
709                                const progress = (lenis.scroll.y - self.start) / (self.end - self.start);
710                                gsap.set(container, { x: -(totalWidth - window.innerWidth) * Math.max(0, Math.min(1, progress)) });
711                            }
712                        }
713                    }
714                });
715
716                // 使用Lenis的滚动事件来同步动画进度
717                if (lenis) {
718                    const updateScroll = () => {
719                        if (triggerInstance) {
720                            const { start, end } = triggerInstance;
721                            // 检查当前滚动位置是否在trigger范围内
722                            if (lenis.scroll.y >= start && lenis.scroll.y <= end) {
723                                const progress = (lenis.scroll.y - start) / (end - start);
724                                gsap.set(container, { x: -(totalWidth - window.innerWidth) * Math.max(0, Math.min(1, progress)) });
725                            }
726                        }
727                    };
728
729                    lenis.on('scroll', updateScroll);
730                }
731            }
732        }
733    },
734	    homefeedTimesB: function () {
735
736        whirPublic.config.pageFeedTime();
737        // 按钮控制横向切换效果
738        if (typeof gsap !== 'undefined') {
739            const container = $('.feed-times');
740            const content = container.find('.item');
741            const prevBtn = $('.feed-btn .prev');
742            const nextBtn = $('.feed-btn .next');
743
744            if (content.length > 0) {
745                let totalWidth = 0;
746                content.each(function () {
747                    totalWidth += $(this).outerWidth();
748                });
749
750                container.css('width', totalWidth + 'px');
751
752                const maxScroll = totalWidth - window.innerWidth;
753                let currentIndex = 0;
754                const itemWidth = window.innerWidth;
755                const totalSlides = content.length;
756
757                const updateButtonStates = () => {
758                    if (currentIndex === 0) {
759                        prevBtn.addClass('disabled');
760                    } else {
761                        prevBtn.removeClass('disabled');
762                    }
763
764                    const maxIndex = Math.ceil(maxScroll / itemWidth);
765                    if (currentIndex >= maxIndex || (currentIndex === totalSlides - 1 && maxScroll <= 0)) {
766                        nextBtn.addClass('disabled');
767                    } else {
768                        nextBtn.removeClass('disabled');
769                    }
770                };
771
772                const goToSlide = (index) => {
773                    const clampedIndex = Math.max(0, Math.min(index, Math.ceil(maxScroll / itemWidth)));
774                    currentIndex = clampedIndex;
775                    const targetX = -currentIndex * itemWidth;
776                    
777                    gsap.to(container, {
778                        x: Math.max(-maxScroll, Math.min(0, targetX)),
779                        duration: 0.5,
780                        ease: 'power2.out'
781                    });
782
783                    updateButtonStates();
784                };
785
786                updateButtonStates();
787
788                prevBtn.click(() => {
789                    if (!prevBtn.hasClass('disabled')) {
790                        goToSlide(currentIndex - 1);
791                    }
792                });
793
794                nextBtn.click(() => {
795                    if (!nextBtn.hasClass('disabled')) {
796                        goToSlide(currentIndex + 1);
797                    }
798                });
799
800                const handleKeydown = (e) => {
801                    if (e.key === 'ArrowLeft') {
802                        e.preventDefault();
803                        goToSlide(currentIndex - 1);
804                    } else if (e.key === 'ArrowRight') {
805                        e.preventDefault();
806                        goToSlide(currentIndex + 1);
807                    }
808                };
809
810                $(document).on('keydown', handleKeydown);
811
812                $(window).on('resize', () => {
813                    const newMaxScroll = totalWidth - window.innerWidth;
814                    goToSlide(currentIndex);
815                });
816            }
817        }
818    },
819    addSoundWave: function () {
820        $('.sound__wave').each(function () {
821            var _this = $(this);
822            var waveNum = 25;
823            for (var i = 0; i < waveNum; i++) {
824                _this.append('<div class="wave"></div>');
825            }
826        });
827    },
828    audioPlay() {
829        whirPublic.config.addSoundWave();
830
831        const playBtn = $('#playAuto');
832        if (!playBtn.length) return;
833
834        const audioSrc = playBtn.data('files');
835        const audio = new Audio(audioSrc);
836        let isPlaying = false;
837
838        // 格式化时间为MM:SS格式
839        const formatTime = (seconds) => {
840            const mins = Math.floor(seconds / 60);
841            const secs = Math.floor(seconds % 60);
842            return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
843        };
844
845        // 获取音频总时间
846        audio.addEventListener('loadedmetadata', () => {
847            const totalTime = formatTime(audio.duration);
848            playBtn.find('.totalTime').text(totalTime);
849        });
850
851        // 更新当前播放时间
852        audio.addEventListener('timeupdate', () => {
853            const currentTime = formatTime(audio.currentTime);
854            playBtn.find('.current-time').text(currentTime);
855        });
856
857        // 播放结束时重置状态
858        audio.addEventListener('ended', () => {
859            isPlaying = false;
860            playBtn.removeClass('isplaying');
861            playBtn.removeClass('ispause');
862            audio.currentTime = 0;
863            playBtn.find('.current-time').text('00:00');
864        });
865
866        // 点击播放/暂停
867        playBtn.on('click', () => {
868            if (isPlaying) {
869                audio.pause();
870                // 未播放完暂停时,不去除isplaying样式,但添加ispause类
871                playBtn.addClass('ispause');
872            } else {
873                audio.play();
874                playBtn.addClass('isplaying');
875                playBtn.removeClass('ispause');
876            }
877            isPlaying = !isPlaying;
878        });
879
880        // 预加载音频以获取总时间
881        audio.load();
882    },
883    about() {
884        $(".thumb-line").each(function () {
885            var totalPoints = $(this).data("points") || 11;
886            var midIndex = Math.floor(totalPoints / 2);
887
888            for (var i = 0; i < totalPoints; i++) {
889                var className = i === midIndex ? 'mid' : i < midIndex ? 'ml' + (midIndex - i) : 'mr' + (i - midIndex);
890                $(this).append(`<i class="${className}"></i>`);
891            }
892        })
893        var realCount = $(".swiper-year .swiper-slide").length;
894        var centerIndex = Math.floor(realCount / 2);
895
896        var swiper = new Swiper(".swiper-year .swiper", {
897            loop: false,
898            slidesPerView: 5,
899            centeredSlides: true,
900            centeredSlidesBounds: true,
901            initialSlide: centerIndex,
902            allowTouchMove: false,
903            breakpoints: {
904                1024: {
905                    slidesPerView: 11,
906                }
907            }
908        });
909        var swiper2 = new Swiper(".swiper-con .swiper", {
910            loop: true,
911            centeredSlides: true,
912            centeredSlidesBounds: true,
913            initialSlide: centerIndex,
914            spaceBetween: 10,
915            slidesPerView: 3,
916            autoplay: {
917                delay: 3000,
918                disableOnInteraction: false,
919                pauseOnMouseEnter: true,
920            },
921            thumbs: {
922                swiper: swiper,
923            },
924            breakpoints: {
925                1024: {
926                    spaceBetween: 50,
927                }
928            }
929        });
930    },
931    jobs() {
932        $(".job-lists .item").on("click", ".name", function () {
933            const $this = $(this);
934            const $item = $this.parents(".item");
935            const $con = $this.next(".con");
936            const $icon = $item.find("i");
937            const isOpen = $con.is(":visible");
938            const animationDuration = 400;
939
940            // 收起所有其他内容
941            const $otherCons = $(".job-lists .item .con").not($con);
942            $otherCons.slideUp(animationDuration);
943            $(".job-lists .item i").not($icon).attr("data-icon", "plus");
944
945            // 切换当前内容
946            $con.slideToggle(animationDuration);
947            $icon.attr("data-icon", isOpen ? "plus" : "mins");
948
949            // 如果是展开操作,在所有动画完成后滚动到当前item顶部
950            if (!isOpen && lenis) {
951                // 使用setTimeout确保所有动画完成
952                setTimeout(() => {
953                    // 计算滚动目标位置,减去header高度
954                    const _top = $item.offset().top - $('header').height();
955                    lenis.scrollTo(_top, {
956                        duration: 0.8,
957                        easing: "easeOutQuart"
958                    });
959                }, animationDuration);
960            }
961        });
962        //默认第一个
963        $(".job-lists .item:first-child .con").show();
964        $(".job-lists .item:first-child i").attr("data-icon", "mins");
965    },
966    resetScrollAni: function () {
967        window.scrollAnimate.elem = document.querySelectorAll(window.scrollAnimate.className);
968        window.scrollAnimate.getPosition();
969        window.scrollAnimate.scrollEvent();
970    },
971    listLoading: function (columnid, showSize) {
972        // 检查元素是否在视窗内(增加100px提前量)
973        function isElementInViewport(el) {
974            const rect = el.getBoundingClientRect();
975            const windowHeight = window.innerHeight || document.documentElement.clientHeight;
976            // 元素顶部距离视窗底部小于等于100px时触发加载
977            return rect.top <= windowHeight - windowHeight * 0.2;
978        }
979
980        var kg = 0;
981        var hasMore = true;
982        var $moreLoading = $(".more-loading");
983        var $loadingList = $("#loading-list");
984
985        // 加载更多数据
986        function loadMore() {
987            // 如果正在加载中或没有更多数据,则不执行
988            if (kg == 1 || !hasMore) return;
989
990            kg = 1;
991            var num = $loadingList.find(".item").length;
992
993            $.ajax({
994                url: commonPath + "ashx/science.ashx?size=" + num + "&showSize=" + showSize + "&cid=" + columnid,
995                cache: false,
996                success: function (data) {
997                    kg = 0;
998                    if (data == "") {
999                        hasMore = false;
1000                        $moreLoading.hide();
1001                    } else {
1002                        $loadingList.append(data);
1003                        //执行无图
1004                        whirPublic.config.toNoPic();
1005                        // 重新初始化滚动动画
1006                        if (window.scrollAnimate) {
1007                            // 重新获取元素位置并检查动画
1008                            whirPublic.config.resetScrollAni();
1009                        }
1010                    }
1011                },
1012                error: function () {
1013                    // 错误时重置加载状态
1014                    kg = 0;
1015                }
1016            });
1017        }
1018
1019        // 使用lenis监听滚动事件,添加防抖动处理
1020        if (lenis) {
1021            let lastLoadTime = 0;
1022            const loadThreshold = 500; // 500ms内只能触发一次加载
1023
1024            lenis.on('scroll', function () {
1025                const currentTime = Date.now();
1026                // 防止滚动抖动频繁触发
1027                if (currentTime - lastLoadTime > loadThreshold) {
1028                    if (isElementInViewport($moreLoading[0])) {
1029                        loadMore();
1030                        lastLoadTime = currentTime;
1031                    }
1032                }
1033            });
1034        }
1035
1036        // 初始检查
1037        setTimeout(function () {
1038            if (isElementInViewport($moreLoading[0])) {
1039                loadMore();
1040            }
1041        }, 100);
1042    },
1043    convertToSvgOutline: function (textColor) {
1044        const elements = document.querySelectorAll('.text-stroke');
1045        elements.forEach((div) => {
1046            const text = div.getAttribute('data-name') || div.textContent.trim();
1047            //div.innerHTML = text;
1048            const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
1049            svg.setAttribute('class', 'svg-text-outline');
1050            svg.setAttribute('viewBox', '0 0 1920 140');
1051            svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
1052
1053            const isStart = div.hasAttribute('data-start');
1054            const xVal = isStart ? '0%' : '50%';
1055            const anchorVal = isStart ? 'start' : 'middle';
1056
1057            const outlineTextElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
1058            outlineTextElement.setAttribute('x', xVal);
1059            outlineTextElement.setAttribute('y', '-5%');
1060            outlineTextElement.setAttribute('text-anchor', anchorV
1060al);
1061            outlineTextElement.setAttribute('dominant-baseline', 'hanging');
1062            outlineTextElement.setAttribute('class', 'svg-text-content svg-text-content-outline');
1063            outlineTextElement.setAttribute('fill', 'transparent');
1064            outlineTextElement.setAttribute('stroke', textColor || '#e1e1e1');
1065            // outlineTextElement.setAttribute('opacity', '0.68');
1066            outlineTextElement.setAttribute('stroke-width', '1');
1067            outlineTextElement.textContent = text;
1068            const fillTextElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
1069            fillTextElement.setAttribute('x', xVal);
1070            fillTextElement.setAttribute('y', '-5%');
1071            fillTextElement.setAttribute('text-anchor', anchorVal);
1072            fillTextElement.setAttribute('dominant-baseline', 'hanging');
1073            fillTextElement.setAttribute('class', 'svg-text-content svg-text-content-fill');
1074            fillTextElement.setAttribute('fill', '#fff');
1075            fillTextElement.setAttribute('stroke', 'none');
1076            fillTextElement.textContent = text;
1077            svg.appendChild(outlineTextElement);
1078            svg.appendChild(fillTextElement);
1079            div.appendChild(svg);
1080        });
1081    },
1082    card: function () {
1083        $(".cards-list .item").each(function () {
1084            let _img = $(this).find("img").attr("src")
1085            $(this).find(".pic").append("<div class='reserve-img'><img src='" + _img + "'></div>")
1086        });
1087        whirPublic.config.convertToSvgOutline()
1088    },
1089    schoolSocial: function () {
1090        $(".swiper").each(function () {
1091            var _this = $(this)[0]
1092            var _page = $(this).find(".controls-pages")[0]
1093            new Swiper(_this, {
1094                loop: true,
1095                speed: 500,
1096                autoplay: {
1097                    delay: 4000,
1098                    disableOnInteraction: false
1099                },
1100                pagination: {
1101                    el: _page,
1102                    clickable: true,
1103                },
1104            });
1105        })
1106    },
1107    behindTour: function () {
1108        new Swiper(".swiper-imgs .swiper", {
1109            speed: 500,
1110            slidesPerView: 3,
1111            loopAdditionalSlides: 3,
1112            spaceBetween: 10,
1113            centeredSlides: true,
1114            loop: true,
1115            navigation: {
1116                nextEl: ".swiper-imgs .next",
1117                prevEl: ".swiper-imgs .prev",
1118            },
1119            allowTouchMove: false,
1120        });
1121    },
1122    bookingInq() {
1123        $(".swiper").each(function () {
1124            var _this = $(this)[0]
1125            var _prev = $(this).find(".prev")[0]
1126            var _next = $(this).find(".next")[0]
1127            new Swiper(_this, {
1128                loop: true,
1129                speed: 500,
1130                autoplay: {
1131                    delay: 3000,
1132                    disableOnInteraction: false
1133                },
1134                navigation: {
1135                    nextEl: _next,
1136                    prevEl: _prev,
1137                },
1138            });
1139        })
1140        whirPublic.config.convertToSvgOutline('#e0e0e0');
1141    },
1142    faq: function (columnid, lcid, key) {
1143        var _nav = $("#nav" + lcid);
1144        _nav.addClass("active");
1145        if ($(window).width() < 1024) {
1146            var _navofl = _nav.offset().left;
1147            var _navw = _nav.width();
1148            var mw = $(".tips-tab").width();
1149            if (_navofl + _navw > mw) {
1150                $(".tips-tab ul").animate({ scrollLeft: (_navofl + _navw) + "px" }, 10)
1151            }
1152        }
1153        // 检查元素是否在视窗内(增加100px提前量)
1154        function isElementInViewport(el) {
1155            const rect = el.getBoundingClientRect();
1156            const windowHeight = window.innerHeight || document.documentElement.clientHeight;
1157            // 元素顶部距离视窗底部小于等于100px时触发加载
1158            return rect.top <= windowHeight - windowHeight * 0.2;
1159        }
1160
1161        var kg = 0;
1162        var hasMore = true;
1163        var $moreLoading = $(".more-loading");
1164        var $loadingList = $("#loading-list");
1165
1166        // 加载更多数据
1167        function loadMore() {
1168            // 如果正在加载中或没有更多数据,则不执行
1169            if (kg == 1 || !hasMore) return;
1170
1171            kg = 1;
1172            var num = $loadingList.find(".box").length;
1173
1174            $.ajax({
1175                url: commonPath + "ashx/faq.ashx?size=" + num + "&lcid=" + lcid + "&cid=" + columnid + "&key=" + key,
1176                cache: false,
1177                success: function (data) {
1178                    kg = 0;
1179                    if (data == "") {
1180                        hasMore = false;
1181                        $moreLoading.hide();
1182                    } else {
1183                        $loadingList.append(data);
1184                        // 初始化点击事件
1185                        toggleAnswer();
1186                        // 重新初始化滚动动画
1187                        if (window.scrollAnimate) {
1188                            // 重新获取元素位置并检查动画
1189                            whirPublic.config.resetScrollAni();
1190                        }
1191                    }
1192                },
1193                error: function () {
1194                    // 错误时重置加载状态
1195                    kg = 0;
1196                }
1197            });
1198        }
1199
1200        // 使用lenis监听滚动事件,添加防抖动处理
1201        if (lenis) {
1202            let lastLoadTime = 0;
1203            const loadThreshold = 500; // 500ms内只能触发一次加载
1204
1205            lenis.on('scroll', function () {
1206                const currentTime = Date.now();
1207                // 防止滚动抖动频繁触发
1208                if (currentTime - lastLoadTime > loadThreshold) {
1209                    if (isElementInViewport($moreLoading[0])) {
1210                        loadMore();
1211                        lastLoadTime = currentTime;
1212                    }
1213                }
1214            });
1215        }
1216
1217        // 初始检查
1218        setTimeout(function () {
1219            if (isElementInViewport($moreLoading[0])) {
1220                loadMore();
1221            }
1222        }, 100);
1223
1224        //点击标题打开内容
1225        function toggleAnswer() {
1226            $(".faq-list .box").each(function () {
1227                var _tit = $(this).find(".tit");
1228                // 先移除已有的click事件监听器,避免重复绑定
1229                _tit.off("click");
1230                _tit.click(function () {
1231                    $(this).parents(".box").toggleClass("active");
1232                    $(this).next(".answer").slideToggle();
1233                    $(this).parents(".box").siblings().removeClass("active");
1234                    $(this).parents(".box").siblings().find(".answer").slideUp();
1235                });
1236            })
1237            //默认第一个 - 只在页面初始化时检查第一个box是否有active样式
1238            var firstBox = $(".faq-list .box:first-child");
1239            if (!firstBox.hasClass("active")) {
1240                firstBox.addClass("active");
1241                firstBox.find(".answer").show();
1242            }
1243        }
1244
1245        toggleAnswer();
1246    },
1247    park: function (cid) {
1248        $(".parkNav-map .left li").click(function () {
1249            var id = $(this).data("id")
1250            $(".area dl[data-id='" + id + "']").fadeIn().siblings().hide();
1251            $(".map-img[data-id='" + id + "']").fadeIn().siblings().hide();
1252            $(this).addClass("active").siblings().removeClass("active");
1253
1254            var _top = $(".parkNav-map").offset().top - $('header').height()
1255            lenis.scrollTo(_top, {
1256                duration: 0.8,
1257                easing: "easeOutQuart"
1258            })
1259        });
1260        if (!cid) {
1261            $(".parkNav-map .left li:first-child").click();
1262        } else {
1263            $(".parkNav-map .left li[data-id='" + cid + "']").addClass("active").siblings().removeClass("active");
1264            $(".area dl[data-id='" + cid + "']").fadeIn().siblings().hide();
1265            $(".map-img[data-id='" + cid + "']").fadeIn().siblings().hide();
1266            setTimeout(function () {
1267                var _top = $(".parkNav-map").offset().top - $('header').height()
1268                lenis.scrollTo(_top, {
1269                    duration: 0.8,
1270                    easing: "power1.out"
1271                })
1272            }, 500)
1273        }
1274    },
1275    explain: function () {
1276        new Swiper(".swiper", {
1277            loop: true,
1278            speed: 500,
1279            autoplay: {
1280                delay: 3000,
1281                disableOnInteraction: false
1282            },
1283            pagination: {
1284                el: ".controls-pages",
1285                clickable: true,
1286            },
1287        });
1288    },
1289    pageFeedTime: function (boxid) {
1290        var _con = $(".feed-times .first-column")
1291        if ($(".home-feed").length > 0) {
1292            _con.insertBefore($(".items .item").eq(0).find(".left .con"));
1293        } else {
1294            _con.insertBefore($(".items .item").eq(0).find(".container"));
1295        }
1296        whirPublic.config.convertToSvgOutline('#96d740');
1297        if (boxid) {
1298            setTimeout(() => {
1299                lenis.scrollTo('[data-boxid="' + boxid + '"]', {
1300                    duration: 0.8,
1301                    easing: "easeOutQuart"
1302                })
1303            }, 400);
1304        }
1305    },
1306    areaList: function () {
1307        whirPublic.config.convertToSvgOutline();
1308    },
1309    highlights: function () {
1310        new Swiper('.highlights .swiper', {
1311            slidesPerView: 3,
1312            spaceBetween: 10,
1313            centeredSlides: true,
1314            loop: true,
1315            navigation: {
1316                nextEl: '.highlights .next',
1317                prevEl: '.highlights .prev',
1318            },
1319            breakpoints: {
1320                1024: {
1321                    spaceBetween: 30,
1322                },
1323            }
1324        });
1325    },
1326    activity: function () {
1327        new Swiper('.activity-common .swiper', {
1328            slidesPerView: 1.3,
1329            spaceBetween: 20,
1330            loop: $(".activity-common .swiper-slide").length > 3 ? true : false,
1331            navigation: {
1332                nextEl: '.highlights .next',
1333                prevEl: '.highlights .prev',
1334            },
1335            breakpoints: {
1336                768: {
1337                    slidesPerView: 2,
1338                    spaceBetween: 20,
1339                },
1340                1024: {
1341                    slidesPerView: 3,
1342                    spaceBetween: 40,
1343                },
1344            },
1345        });
1346    },
1347    volunteer:function(){
1348        $(".page-nav li a").click(function(){
1349            let _id=$(this).data("id");
1350            $(this).parent("li").addClass("aon");
1351            $(this).parent("li").siblings().removeClass("aon");
1352            $(".cbox[data-id='" + _id + "']").show().siblings().hide();
1353        });
1354        $(".page-nav li").eq(0).find("a").click();
1355    }
1356}
1357
1358
1359/**
1360 * 触发效果
1361 */
1362whirPublic.open = {
1363    /**鼠标悬停效果
1364     * @param {string} m - 效果主体选择器
1365     * @param {string} className - 效果样式
1366     * @returns {void}
1367     */
1368    hoverCss: function (m, className) {
1369        $(m).hover(function () {
1370            $(this).toggleClass(className ? className : 'show');
1371        });
1372    },
1373    /**点击触发效果
1374     * @param {string} btn - 触发效果的按钮选择器
1375     * @param {string} main - 效果主体选择器
1376     * @param {string} css - 效果样式
1377     * @param {string} outside - 点击外部区域关闭效果
1378     * @returns {void}
1379     */
1380    clickOpen: function (btn, main, css, outside) {
1381        $(btn).click(function (e) {
1382            $(main).toggleClass(css);
1383            $(document).on("click", function () {
1384                $(main).removeClass(css);
1385            });
1386            e.stopPropagation();
1387        });
1388        $(outside).on("click", function (e) {
1389            e.stopPropagation();
1390        });
1391    },
1392    /**WAP端菜单
1393     * @param {string} m - 菜单容器选择器
1394     * @param {string} nav - 菜单导航选择器
1395     * @param {string} dl - 菜单列表选择器
1396     * @param {string} dt - 菜单标题选择器
1397     * @param {string} dd - 菜单子项选择器
1398     * @returns {void}
1399     */
1400    wapToMenu: function (m, nav, dl, dt, dd) {
1401        $(m).each(function () {
1402            var _this = $(this).find(nav).find(dl);
1403            _this.each(function () {
1404                var e = $(this).find(dd).length
1405                if (e >= 1) {
1406                    $(this).find(dt).append('<span class="wap-icon__open"></span>');
1407                }
1408            });
1409        });
1410        $(".wap-icon__open").click(function () {
1411            var _dl = $(this).parents(dl)
1412            _dl.find(dd).slideToggle();
1413            _dl.addClass("active");
1414            _dl.siblings().removeClass("active");
1415            _dl.siblings().find(dd).slideUp();
1416        });
1417    },
1418    /**
1419     * 弹窗视频
1420     * @param {string} btn - 触发弹窗视频的按钮选择器
1421     * @returns {void}
1422     */
1423    videoOpen: function () {
1424        $("[data-play]").click(function () {
1425            var files = $(this).data("files");
1426            var data = $(this).data("name");
1427            var directives = $(this).data("play");
1428            var parent = $(this).data("parent-class");
1429
1430            if (directives == 'outside') {
1431                $("[data-popup='video']").remove();
1432                var titleArea = '';
1433                if (data && (data.title || data.intro)) {
1434                    titleArea = '<div class="tit-area"><div class="title">' + (data.title || '') + '</div><div class="edit-info">' + (data.intro || '') + '</div></div>';
1435                }
1436                $('body').append("<div data-popup='video'  data-flex='aic-jcc'><section class='inner'><div class='close'></div><video src='" + files + "' controls autoplay  x-webkit-airplay='true' x5-video-player-type='h5' x5-video-player-fullscreen='true' webkit-playsinline='true' playsinline='true' controlslist='nodownload' oncontextmenu='return false;'></video>" + titleArea + "</section></div>");
1437                $('body').css("overflow", "hidden");
1438                videoClose();
1439            } else if (directives == 'inside') {
1440                $(parent).parent("div").find(".inside_video").remove();
1441                $(parent).parent("div").append("<div class='inside_video' data-fullscreen='full-t'><video src='" + files + "' controls autoplay  x-webkit-airplay='true' x5-video-player-type='h5' x5-video-player-fullscreen='true' webkit-playsinline='true' playsinline='true'  controlslist='nodownload' oncontextmenu='return false;'></video></div>");
1442            }
1443        });
1444
1445        function videoClose() {
1446            $("[data-popup='video'] .close").click(function (e) {
1447                $("[data-popup='video']").remove();
1448                $('body').css("overflow", "auto");
1449            });
1450        }
1451    },
1452    /**
1453     * 实现标签页切换功能
1454     * @param {string} m - 标签页容器选择器,包含标签按钮和内容区域的父元素
1455     * @param {string} toggleName - 触发切换的事件类型,'hover'表示鼠标悬停触发,其他值则为点击触发
1456     * @param {string} ul - 自定义标签按钮列表容器选择器,默认为'.tab-ul'
1457     * @param {string} li - 自定义标签按钮元素选择器,默认为'li'
1458     * @param {string} box - 自定义标签内容容器选择器,默认为'.tab-list'
1459     * @param {string} child - 自定义标签内容元素选择器,默认为'.box'
1460     * @param {string} effect - 切换效果类型,可选值:'fade'、'slide',默认无动画
1461     * @param {number} duration - 动画持续时间(毫秒),默认为300
1462     * @returns {void}
1463     */
1464    tabChangeClick: function (m, toggleName, ul, li, box, child, effect, duration) {
1465        $(m ? m : '.whir-tab').each(function () {
1466            // 获取标签按钮容器,如果未提供自定义选择器则使用默认值'.tab-ul'
1467            var _ul = $(this).find(ul ? ul : '.whir-tab__ul')
1468            // 获取所有标签按钮,如果未提供自定义选择器则使用默认值'li'
1469            var _li = _ul.find(li ? li : '.whir-tab__li')
1470            // 获取标签内容容器,如果未提供自定义选择器则使用默认值'.tab-list'
1471            var _box = $(this).find(box ? box : '.whir-tab__list')
1472            // 获取所有标签内容元素,如果未提供自定义选择器则使用默认值'.box'
1473            var _child = _box.find(child ? child : '.whir-tab__box')
1474            // 设置动画持续时间,默认为300毫秒
1475            var animDuration = duration || 300;
1476
1477            // 默认激活第一个标签按钮
1478            _li.eq(0).addClass("active");
1479            // 默认显示第一个标签内容
1480            if (effect == 'fade' || effect == 'slide') {
1481                _child.eq(0).show().siblings().hide();
1482            } else {
1483                _child.eq(0).addClass("active");
1484            }
1485
1486            // 根据toggleName参数确定触发事件类型,'hover'对应鼠标悬停,其他值对应点击事件
1487            var event = toggleName === 'hover' ? 'mouseover' : 'click';
1488            // 为所有标签按钮绑定事件
1489            _li.on(event, function () {
1490                // 获取当前标签按钮的索引
1491                var e = $(this).index();
1492                // 激活当前标签按钮,移除其他按钮的激活状态
1493                $(this).addClass("active").siblings().removeClass("active");
1494
1495                // 根据效果类型应用不同的动画
1496                if (effect === 'fade') {
1497                    // 淡入淡出效果
1498                    _child.hide();
1499                    _child.eq(e).fadeIn(animDuration);
1500                } else if (effect === 'slide') {
1501                    // 滑动效果
1502                    _child.stop(true, true).slideUp(animDuration);
1503                    _child.eq(e).stop(true, true).slideDown(animDuration);
1504                } else {
1505                    // 无动画效果,直接切换显示状态
1506                    _child.eq(e).addClass("active").siblings().removeClass("active")
1507                }
1508            });
1509        });
1510    }
1511}
1512
1513/**
1514  * 搜索功能模块
1515  * cn: 中文搜索
1516  * en: 英文搜索
1517  * 其他语言搜索cn,en都可用,更改后面参数即可
1518  */
1519whirPublic.search = {
1520    cn: function (btn, kid, url, txt, txt1, txt2, txt3) {
1521        $(btn).jqSearch({
1522            TxtVal: txt ? txt : "请输入关键字",
1523            KeyTxt1: txt1 ? txt1 : "输入关键词搜索!",
1524            KeyTxt2: txt2 ? txt2 : "输入的关键词字数不要过多!",
1525            KeyTxt3: txt3 ? txt3 : "您输入的内容存在特殊字符!",
1526            KeyId: kid, //输入框id
1527            KeyUrl: url, //跳转链接
1528            KeyHref: "key", //链接传值
1529            Static: false //是否静态站
1530        });
1531    },
1532    en: function (btn, kid, url, txt, txt1, txt2, txt3) {
1533        $(btn).jqSearch({
1534            TxtVal: txt ? txt : "Enter keywords",
1535            KeyTxt1: txt1 ? txt1 : "Enter keywords to search!",
1536            KeyTxt2: txt2 ? txt2 : "Don't enter too many keywords!",
1537            KeyTxt3: txt3 ? txt3 : "The content you entered contains special characters!",
1538            KeyId: kid, //输入框id
1539            KeyUrl: url, //跳转链接
1540            KeyHref: "key", //链接传值
1541            Static: false //是否静态站
1542        });
1543    }
1544}
1545/**
1546 * 视频轮播功能模块
1547 */
1548let publicSwiperVideo;
1549whirPublic.video = {
1550    /**
1551     * 初始化视频轮播
1552     * @param {string} name - 轮播容器选择器
1553     * @param {array} arr - 轮播图片数组
1554     * @param {string} ef - 轮播切换效果,默认为 'fade'
1555     * @param {string} pt - 分页器类型,默认为 'bullets'
1556     * @returns {void}
1557     */
1558    banner: function (name, arr, ef, pt) {
1559        // 确保 arr 是数组
1560        arr = Array.isArray(arr) ? arr : [];
1561
1562        // 定义默认选择器
1563        var defaultSelectors = [
1564            '[data-videoswiper]',
1565            '.swiper-slide',
1566            '.controls-page',
1567            '.total',
1568            '.cur',
1569            '.next',
1570            '.prev',
1571            '.pause'
1572        ];
1573
1574        // 合并默认选择器和用户提供的选择器
1575        var newArr = [];
1576        for (var i = 0; i < defaultSelectors.length; i++) {
1577            // 如果用户提供了选择器且不为null,则使用用户的选择器,否则使用默认选择器
1578            newArr.push(name + " " + (arr[i] || defaultSelectors[i]));
1579        }
1580
1581        let num_total = rang($(newArr[1]).length, 9)
1582        $(newArr[3]).text(num_total);
1583
1584        whirPublic.video.addVideo(newArr[1]);
1585        whirPublic.video.main(newArr[0], ef, newArr[2], pt, newArr[5], newArr[6], newArr[7]);
1586        publicSwiperVideo.on('slideChange', function () {
1587            var index = publicSwiperVideo.realIndex;
1588            var num = index + 1
1589            var num_real = rang(num, 9);
1590            $(newArr[4]).text(num_real);
1591        })
1592
1593        function rang(a, b) {
1594            return a <= b ? '0' + a : a;
1595        }
1596    },
1597    /**
1598     * 配置并初始化 Swiper 视频轮播
1599     * @param {string} container - 轮播容器选择器
1600     * @param {string} effect - 轮播切换效果,默认为 'fade'
1601     * @param {string} paginationEl - 分页器选择器
1602     * @param {string} paginationType - 分页器类型,默认为 'bullets'
1603     * @param {string} nextButton - 下一个按钮选择器
1604     * @param {string} prevButton - 上一个按钮选择器
1605     * @param {string} pauseButton - 暂停/播放按钮选择器
1606     * @returns {void}
1607     */
1608    main: function (container, effect, paginationEl, paginationType, nextButton, prevButton, pauseButton) {
1609        publicSwiperVideo = new Swiper(container, {
1610            loop: true,
1611            speed: 1200,
1612            autoplay: {
1613                delay: 4000,
1614                disableOnInteraction: false
1615            },
1616            effect: effect ? effect : 'fade',
1617            fadeEffect: {
1618                crossFade: true,
1619            },
1620            pagination: {
1621                el: paginationEl,
1622                type: paginationType ? paginationType : 'bullets',
1623                clickable: true,
1624                renderBullet: function (index, className) {
1625                    return '<span class="' + className + ' swiper-pagination-svg-bullet" data-index="' + index + '">' +
1626                        '<svg class="progress-circle" width="30" height="30" viewBox="0 0 30 30">' +
1627                        '<circle class="progress-circle-bg" cx="15" cy="15" r="12" fill="none" stroke="rgba(59, 130, 246, 0.2)" stroke-width="1"/>' +
1628                        '<circle class="progress-circle-active" cx="15" cy="15" r="12" fill="none" stroke="#3b82f6" stroke-width="2" stroke-dasharray="75.4" stroke-dashoffset="75.4" transform="rotate(-90 15 15)"/>' +
1629                        '</svg>' +
1630                        '</span>';
1631                },
1632                formatFractionCurrent: function (number) {
1633                    return number < 10 ? '0' + number : number;
1634                },
1635                formatFractionTotal: function (number) {
1636                    return number < 10 ? '0' + number : number;
1637                }
1638            },
1639            navigation: {
1640                nextEl: nextButton,
1641                prevEl: prevButton,
1642            },
1643            on: {
1644                init: function () {
1645                    for (let index = 0; index < this.slides.length; index++) {
1646                        const element = this.slides[index];
1647                        if ($(element).find('video').length > 0) {
1648                            $(element).find('video').attr("id", "banner-video__" + index)
1649                        }
1650                    }
1651                    playVideo(this.slides, this.activeIndex);
1652
1653                    // 初始化暂停/播放按钮
1654                    initPauseButton();
1655
1656                    // 初始化SVG分页器动画
1657                    initSVGPaginationAnimation(this);
1658                },
1659                slideChange: function () {
1660                    for (let index = 0; index < this.slides.length; index++) {
1661                        const element = this.slides[index];
1662                        if ($(element).find('video').length > 0) {
1663                            $(element).find('video')[0].pause();
1664                            $(element).find('video')[0].currentTime = 0;
1665                            $(element).find('video')[0].muted = true;
1666                            $(element).find('.video-inner').removeClass("active");
1667                        }
1668                    }
1669                    playVideo(this.slides, this.activeIndex);
1670
1671                    // 更新SVG分页器动画
1672                    updateSVGPaginationAnimation(this);
1673                },
1674                autoplayStart: function () {
1675                    // 轮播开始自动播放时,恢复SVG动画
1676                    resumeSVGAnimation();
1677                },
1678                autoplayStop: function () {
1679                    // 轮播停止自动播放时,暂停SVG动画
1680                    pauseSVGAnimation();
1681                }
1682            }
1683        });
1684
1685        // 初始化暂停/播放按钮功能
1686        function initPauseButton() {
1687            if (pauseButton) {
1688                $(pauseButton).off('click').on('click', function () {
1689                    if (publicSwiperVideo && publicSwiperVideo.autoplay) {
1690                        if (publicSwiperVideo.autoplay.running) {
1691                            // 当前正在自动播放,点击暂停
1692                            publicSwiperVideo.autoplay.stop();
1693                            $(this).addClass('play');
1694
1695                            // 暂停SVG动画
1696                            pauseSVGAnimation();
1697                        } else {
1698                            // 当前已暂停,点击播放
1699                            publicSwiperVideo.autoplay.start();
1700                            $(this).removeClass('play');
1701
1702                            // 恢复SVG动画
1703                            resumeSVGAnimation();
1704                        }
1705                    }
1706                });
1707            }
1708        }
1709
1710        // 暂停SVG动画
1711        function pauseSVGAnimation() {
1712            const bullets = $(paginationEl).find('.swiper-pagination-svg-bullet');
1713            bullets.each(function () {
1714                const $bullet = $(this);
1715                const $activeCircle = $bullet.find('.progress-circle-active');
1716
1717                if ($bullet.hasClass('active')) {
1718                    // 暂停当前活动分页器的动画
1719                    $activeCircle[0].style.animationPlayState = 'paused';
1720                }
1721            });
1722        }
1723
1724        // 恢复SVG动画
1725        function resumeSVGAnimation() {
1726            const bullets = $(paginationEl).find('.swiper-pagination-svg-bullet');
1727            bullets.each(function () {
1728                const $bullet = $(this);
1729                const $activeCircle = $bullet.find('.progress-circle-active');
1730
1731                if ($bullet.hasClass('active')) {
1732                    // 重新启动动画,从头开始
1733                    $activeCircle[0].style.animation = 'none';
1734                    setTimeout(() => {
1735                        $activeCircle[0].style.animation = 'circle-progress 4s linear infinite';
1736                    }, 10);
1737                }
1738            });
1739        }
1740
1741        // 初始化SVG分页器动画
1742        function initSVGPaginationAnimation(swiper) {
1743            updateSVGPaginationAnimation(swiper);
1744        }
1745
1746        // 更新SVG分页器动画
1747        function updateSVGPaginationAnimation(swiper) {
1748            const bullets = $(paginationEl).find('.swiper-pagination-svg-bullet');
1749            const activeIndex = swiper.realIndex;
1750
1751            bullets.each(function (index) {
1752                const $bullet = $(this);
1753                const $activeCircle = $bullet.find('.progress-circle-active');
1754
1755                if (index === activeIndex) {
1756                    // 当前活动分页器:显示动画圆圈
1757                    $bullet.addClass('active');
1758
1759                    // 启动圆圈动画
1760                    $activeCircle[0].style.animation = '
1760none';
1761                    setTimeout(() => {
1762                        $activeCircle[0].style.animation = 'circle-progress 4s linear infinite';
1763
1764                        // 如果轮播当前是暂停状态,则暂停SVG动画
1765                        if (publicSwiperVideo && publicSwiperVideo.autoplay && !publicSwiperVideo.autoplay.running) {
1766                            $activeCircle[0].style.animationPlayState = 'paused';
1767                        }
1768                    }, 10);
1769                } else {
1770                    // 非活动分页器:隐藏动画圆圈
1771                    $bullet.removeClass('active');
1772                    $activeCircle[0].style.animation = 'none';
1773                }
1774            });
1775        }
1776
1777        function playVideo(slide, index) {
1778            var videoInner = $(slide[index]).find('.video-inner');
1779            var winW = $(window).width() || window.innerWidth;
1780            var t = setTimeout(function () {
1781                if (videoInner.length > 0) {
1782                    if (winW <= whirBreakpoints.mobile) {//WAP
1783                        // 移除之前可能存在的事件监听器,防止重复绑定
1784                        videoInner.find(".open-video").off('click').on("click", function () {
1785                            if (publicSwiperVideo && publicSwiperVideo.autoplay) {
1786                                publicSwiperVideo.autoplay.stop();
1787                            }
1788                            videoInner.addClass("active");
1789
1790                            // 使用jQuery查找视频元素,避免getElementById可能的null问题
1791                            var videoElement = videoInner.find("video")[0];
1792                            if (videoElement) {
1793                                videoElement.play();
1794                                videoElement.muted = false;
1795
1796                                // 移除之前可能存在的ended事件监听器
1797                                $(videoElement).off('ended').on('ended', function () {
1798                                    publicSwiperVideo.slideNext();
1799                                    publicSwiperVideo.autoplay.start();
1800                                    videoInner.removeClass("active");
1801                                });
1802                            }
1803                        });
1804                    } else {//PC
1805                        if (publicSwiperVideo && publicSwiperVideo.autoplay) {
1806                            publicSwiperVideo.autoplay.stop();
1807                        }
1808
1809                        // 使用jQuery查找视频元素,避免getElementById可能的null问题
1810                        var videoElement = videoInner.find("video")[0];
1811                        if (videoElement) {
1812                            videoElement.play();
1813
1814                            // 移除之前可能存在的ended事件监听器
1815                            $(videoElement).off('ended').on('ended', function () {
1816                                publicSwiperVideo.slideNext();
1817                                publicSwiperVideo.autoplay.start();
1818                            });
1819                        }
1820                    }
1821                } else {
1822                    // 如果当前slide不是视频,确保自动播放是启动的
1823                    if (publicSwiperVideo && publicSwiperVideo.autoplay && !publicSwiperVideo.autoplay.running) {
1824                        publicSwiperVideo.autoplay.start();
1825                    }
1826                }
1827                clearTimeout(t);
1828            }, 300);
1829        }
1830    },
1831    /**
1832     * 为轮播项添加视频元素
1833     * @param {string} m - 轮播项选择器
1834     * @returns {void}
1835     */
1836    addVideo: function (m) {
1837        $(m).each(function (e) {
1838            var videoInner = $(this).find(".video-inner");
1839            if (videoInner.length > 0) {
1840                var _files = '';
1841                var files_desktop = videoInner.data("files_desktop");
1842                var files_mobile = videoInner.data("files_mobile");
1843                if (window.innerWidth > whirBreakpoints.mobile) {
1844                    _files = files_desktop;
1845                } else {
1846                    _files = files_mobile ? files_mobile : files_desktop;
1847                }
1848                //添加视频
1849                var isMobile = window.innerWidth <= 1024;
1850                var controlsAttr = isMobile ? ' controls' : '';
1851                videoInner.append("<div class='video-box'><video src='" + _files + "' x-webkit-airplay='true' x5-video-player-type='h5' x5-video-player-fullscreen='true' webkit-playsinline='true' playsinline='true' muted controlslist='nodownload' oncontextmenu='return false;'" + controlsAttr + "></video></div>");
1852			   //添加移动端播放按钮
1853                videoInner.append("<div class='open-video'></div>");
1854            }
1855        })
1856    }
1857}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.