PageSourceSearch

https://www.zywell.net/assets/js/unit/tabs.js

js zywell.net collected 2026-10-04 04:37:14 UTC 8,332 bytes, 232 lines download raw bytes

1class ScriptTabs extends Script {
2  constructor(params) {
3    super(params)
4    this.$dom = document.querySelector(`#${this.attrId}`)
5    this.$swiperButtonPrev = this.$dom.querySelector('.swiper-button-prev')
6    this.$swiperButtonNext = this.$dom.querySelector('.swiper-button-next')
7    this.$tabsHeader = this.$dom.querySelector(`.unit-tabs__header`)
8    this.$tabsNavWrap = this.$dom.querySelector(`.unit-tabs__nav-wrap`)
9    this.$panelWrap = this.$dom.querySelector('.unit-tabs__panel-wrap')
10    this.$tabItems = this.$dom.querySelectorAll('.unit-tabs__nav-item')
11    this.$tabPanels = this.$panelWrap.querySelectorAll('.unit-tabs__panel')
12
13    this.config = JSON.parse(this.utilts.htmlDecode(params.config))
14    this.tab_list = JSON.parse(this.utilts.htmlDecode(params.tab_list).replace(/\n<\s*br\s*\/*\s*>/ig, '<br/>').replace(/\n<\s*p\s*>/ig, '<p>').replace(/<\s*p\s*>\n/ig, '<p>').replace(/\n<\s*\/p\s*>/ig, '</p>'))
15    this.columnsInfo = JSON.parse(this.utilts.htmlDecode(params.columnsInfo))
16    // mock ----------------
17    // params.colInfo = undefined
18    // ----------------------
19    if (params.colInfo) {
20      this.colInfo = JSON.parse(this.utilts.htmlDecode(params.colInfo))
21    } else {
22      let columnsObj = {
23        1: '12',
24        2: '6',
25        3: '4',
26        4: '3',
27        5: '2_4',
28        6: '2',
29        7: '12_7',
30        8: '12_8',
31        9: '12_9',
32        10: '12_10',
33        11: '12_11',
34        12: '1'
35      }
36      this.colInfo = {
37        cols_col: columnsObj[this.columnsInfo.default],
38        cols_md: columnsObj[this.columnsInfo.md],
39        cols_xl: columnsObj[this.columnsInfo.xl],
40      }
41    }
42
43    this.activeIndex = 0
44    if (this.utilts.checkDesign()) { // 装修时,重新渲染保持 activeIndex,避免编辑其他tab的内容总是跳回第一个tab
45      if (!window.tabsState) window.tabsState = {};
46      if (!window.tabsState[this.attrId]) window.tabsState[this.attrId] = { activeIndex: 0 }
47      if (window.tabsState[this.attrId].activeIndex < this.tab_list.length) {
48        this.activeIndex = window.tabsState[this.attrId].activeIndex
49      }
50    }
51
52    // 工具
53    this.utilts = window.app.utilts
54  }
55
56  async init() {
57    // siwper
58    if (!window.Swiper) await new Load('/assets/plugins/swiper.min.js')
59    this.utilts.interObserver(this.$dom, () => { // 避免本组件在display:none的状态初始化,导致swiper样式问题(例如在column-tabs中添加tabs)
60      this.initScreen()
61      window.addEventListener('resize', () => {
62        if (this.currentScreen === this.utilts.getScreen()) return
63        this.initScreen()
64      })
65      this.checkBlank()
66    }, false)
67  }
68  initScreen() {
69    this.currentScreen = this.utilts.getScreen()
70    this.initTab()
71    // this.initContentSwiper()
72    this.initContentSwiperHack()
73  }
74  initTab() {
75    this.$tabsSwiper?.destroy(true, true)
76    if (this.utilts.checkDesign()) {
77      this.$tabItems.forEach((it, i) => { // html 中默认第一个是 active,需要同步一下 window.tabsState[this.attrId].activeIndex
78        if (i === this.activeIndex) {
79          it.classList.add('active')
80        } else {
81          it.classList.remove('active')
82        }
83      })
84    }
85
86    const swiperConfig = {
87      slidesPerView: this.columnsInfo[this.currentScreen],
88      spaceBetween: 0,
89      initialSlide: this.activeIndex,
90      // slideToClickedSlide: true,
91      // centeredSlides: true,
92      freeMode: true,
93      navigation: {
94        prevEl: '.swiper-button-prev',
95        nextEl: '.swiper-button-next',
96      },
97      on: {
98        // slideChange: (swiper) => {
99        //   this.tabClick(swiper, swiper.activeIndex)
100        // },
101      }
102    }
103    
104    this.$dom.querySelector(`#initBetween_${this.attrId}`)?.remove()
105
106    if (['default', 'md'].includes(this.currentScreen)) {
107      this.$swiperButtonPrev.classList.remove('d-none')
108      this.$swiperButtonNext.classList.remove('d-none')
109      this.$tabsHeader.classList.add('swiper-container')
110      this.$tabsNavWrap.classList.add('swiper-wrapper')
111      this.$dom.querySelectorAll(`.unit-tabs__nav-item`).forEach(($item) => {
112        $item.classList.add('swiper-slide')
113      })
114      // setTimeout(() => {
115        swiperConfig.spaceBetween = this.initBetween()
116        this.$tabsSwiper = new Swiper(`#${this.attrId} .unit-tabs__header`, swiperConfig)
117      // },);
118    } else {
119      this.$swiperButtonPrev.classList.add('d-none')
120      this.$swiperButtonNext.classList.add('d-none')
121      this.$tabsHeader.classList.remove('swiper-container')
122      this.$tabsNavWrap.classList.remove('swiper-wrapper')
123      this.$dom.querySelectorAll(`.unit-tabs__nav-item`).forEach(($item) => {
124        $item.classList.remove('swiper-slide')
125      })
126    }
127    this.initEvent()
128  }
129  initBetween() { // 去掉间距,读取间距的值传给swiper处理
130    const itemClass = 'unit-tabs__nav-item'
131    const $itemFirst = this.$dom.querySelector(`.${itemClass}`)
132    if (!$itemFirst) return
133    const style = this.utilts.getNodeStyle($itemFirst)
134    const item_pr = parseFloat(style.paddingRight) || 0
135    const item_pl = parseFloat(style.paddingLeft) || 0
136    const item_mr = parseFloat(style.marginRight) || 0
137    const item_ml = parseFloat(style.marginLeft) || 0
138    
139    
140    const $style = document.createElement('style')
141    $style.setAttribute('id', `initBetween_${this.attrId}`)
142    
143    let html = `#${this.attrId} .unit-tabs__nav-wrap {margin:0!important;}
144    #${this.attrId} .${itemClass}{margin-left:0!important;padding:0!important}
145    #${this.attrId} .col-${this.colInfo.cols_col.toString().replace('.', '_')}, #${this.attrId} .col-md-${this.colInfo.cols_md.toString().replace('.', '_')}, #${this.attrId} .col-xl-${this.colInfo.cols_xl.toString().replace('.', '_')} {max-width: unset; flex: none}`
146    $style.innerHTML = html
147    this.$dom.appendChild($style)
148    return item_pr + item_pl + item_mr + item_ml
149  }
150  initContentSwiper() {
151    if (this.$contentSwiper) return
152    const swiperConfigBase = {
153      effect: 'fade',
154      initialSlide: this.activeIndex,
155      noSwiping: true,
156      noSwipingClass: 'swiper-slide',
157      speed: 0,
158    }
159    const swiperConfig = {
160      ...swiperConfigBase
161    }
162
163    const swiperConfigMobile = {
164      ...swiperConfigBase,
165      // controller: {
166      //   control: this.$tabsSwiper, // 控制 this.$tabsSwiper
167      // },
168      // on: {
169      //   slideChange: (swiper) => {
170      //     this.tabChange(swiper, swiper.activeIndex)
171      //   }
172      // }
173    }
174    setTimeout(()=>{
175      this.$contentSwiper = new Swiper(`#${this.attrId} .unit-tabs__content`, ['default', 'md'].includes(this.currentScreen) ? swiperConfigMobile : swiperConfig)
176      // if (['default', 'md'].includes(this.currentScreen)) { this.$tabsSwiper.controller.control = this.$contentSwiper }
177    })
178  }
179  initContentSwiperHack() {
180    const slideTo = () => {
181      this.$tabPanels.forEach((tabItem, tabIndex) => {
182        if (tabIndex === this.activeIndex) {
183          tabItem.style.display = ''
184        } else {
185          tabItem.style.display = 'none'
186        }
187      })
188    }
189    slideTo()
190    if (this.$contentSwiper) return
191    this.$contentSwiper = {
192      slideTo
193    }
194  }
195  initEvent() {
196    this.$tabItems.forEach((item, index) => {
197      item.addEventListener('click', (e) => this.tabClick(e, index))
198    })
199  }
200  tabChange(e, index) {
201    if (this.utilts.checkDesign()) {
202      window.tabsState[this.attrId].activeIndex = index
203    }
204    this.activeIndex = index
205    this.$tabItems.forEach(it => it.classList.remove('active'))
206    this.$tabItems[index].classList.add('active')
207
208    this.checkBlank()
209  }
210  tabClick(e, index) {
211    this.tabChange(e, index)
212    if (['default', 'md'].includes(this.currentScreen)) {
213      this.$tabsSwiper.slideTo(index)
214    }
215    this.$contentSwiper.slideTo(index)
216    this.$panelWrap.querySelectorAll('[data-observer]').forEach(($item) => {
217      if ('_observer' in $item && $item._observer instanceof Function) {
218        // 切换时执行图片加载检查
219        $item._observer()
220      } else {
221        $item.removeAttribute('[data-observer]')
222      }
223    })
224  }
225  checkBlank() {
226    if (!this.tab_list[this.activeIndex].tab_panel.length) {
227      this.$dom.classList.add('blank-package')
228    } else {
229      this.$dom.classList.remove('blank-package')
230    }
231  }
232}

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.