PageSourceSearch

https://www.sokany.com/assets/js/unit/product-detail.js?v=2

js sokany.com collected 2026-10-06 13:25:51 UTC 21,678 bytes, 462 lines download raw bytes

1class ScriptProductDetail extends Script {
2  constructor(params) {
3    super(params)
4    this._attrs = {
5      lastWindowWidth: window.innerWidth
6    }
7    this._original = {}
8    this.params = params
9    this.hasBuyOn = params.hasBuyOn
10    this.showContent = JSON.parse(this.utilts.htmlDecode(params.showContent))
11    this.show_quote_list = params.show_quote_list
12    this.isM = window.innerWidth < 768
13    this.$dom = document.querySelector(`#${this.attrId}`)
14    this.$iconGroup = null
15    this.$btnGroup = null
16    this.patternModal = null
17    this.isShop = window.app.info.site.is_shop
18    this.loadingTarget = 96
19    this.loadingTimeout = null
20  }
21  async init() {
22    this.beforeInit()
23    this.checkStatus()
24    await this.initScript()
25    this.afterInit()
26  }
27
28  beforeInit() {
29    const params = this.params
30    this._original.direction = this.params._direction || this.params.direction; // 保存初始的方向
31    const isMobi = window.innerWidth < 1200
32    if (isMobi) {
33      params.direction = 'bottom'
34    }
35  }
36
37  afterInit() {
38    const unit = document.querySelector(`#${this.attrId} .unit-product-detail`);
39    unit && unit.removeAttribute('before-init')
40    const params = this.params
41    const that = this;
42    const allClasses = ['unit-detail-album--x', 'unit-detail-album--y', 'unit-detail-album--top', 'unit-detail-album--bottom', 'unit-detail-album--left', 'unit-detail-album--right']
43    const directMap = {
44      top: {
45        direction: 'horizontal',
46        classList: ['unit-detail-album--y', 'unit-detail-album--top']
47      },
48      bottom: {
49        direction: 'horizontal',
50        classList: ['unit-detail-album--y', 'unit-detail-album--bottom']
51      },
52      left: {
53        direction: 'vertical',
54        classList: ['unit-detail-album--x', 'unit-detail-album--left']
55      },
56      right: {
57        direction: 'vertical',
58        classList: ['unit-detail-album--x', 'unit-detail-album--right']
59      }
60    };
61    const thumb = that.$dom.querySelector('.unit-detail-album__thumb')
62    window.addEventListener('resize', function () {
63      if (Math.abs(that._attrs.lastWindowWidth - window.innerWidth) <= 20) {
64        // 非宽度变化不处理
65        return;
66      }
67      if (!window.hasOwnProperty('_lf_timers_')) {
68        window._lf_timers_ = {}
69      }
70      that._attrs.lastWindowWidth = window.innerWidth
71      const timer = params.attrId + '@resize';
72      if (window._lf_timers_[timer]) {
73        clearTimeout(window._lf_timers_[timer])
74        delete window._lf_timers_[timer]
75      } else {
76        thumb.setAttribute('hidden', 'true')
77      }
78      window._lf_timers_[timer] = setTimeout(function () {
79        delete window._lf_timers_[timer];
80        const isMobi = window.innerWidth < 1200
81        const direct = that._original.direction
82        let direction = !isMobi && direct && directMap.hasOwnProperty(direct) ? direct : 'bottom'
83        const targetClassList = directMap[direction].classList
84        if (isMobi) {
85          unit.classList.add('unit-product-detail-mobi')
86        } else {
87          unit.classList.remove('unit-product-detail-mobi')
88        }
89        that.$dom.classList.remove.apply(that.$dom.classList, allClasses);
90        that.$dom.classList.add.apply(that.$dom.classList, targetClassList);
91        thumb.removeAttribute('hidden')
92        that.unitAlbum.swiperThumb.changeDirection(directMap[direction].direction).update()
93        that.unitAlbum.swiperPicture.update()
94      }, 500)
95    })
96
97    window.addEventListener('evt_open_product_inquiry', function () {
98      document.querySelector('.unit-detail-button-inquiry').click()
99    })
100
101    const promptLogin = this.utilts.throttle(function () {
102      const message = Utilts.ins().$t('Please login in first')
103      if (confirm(message)) window.location.href = globalThis.Server.getRinseHref('/login.html', window.app.info.site)
104    }, 200)
105
106    window.addEventListener('evt_required_login', function () {
107      promptLogin();
108    })
109    setTimeout(() => {
110      if (!this.showContent.includes('btn') || !window.app.info.productDetail.config.button_authority.includes('inquiry')){
111        const allProductInquiryBtns = document.querySelectorAll('.unit-detail-html-tabs__content-html [cloud-editor="unit"][type="cebutton"] [ce-data-type="link"][target="product_inquiry"]')
112        allProductInquiryBtns.forEach(val => {
113          val.classList.add('d-none')
114        })
115      }
116    }, 100)
117  }
118
119  // 移动端执行
120  initMobile() {
121    if (document.querySelector('.unit-detail-footer') || window.app.utilts.checkDesign()) return
122    this.initFooter()
123    this.initBtn()
124    this.initPatternModal()
125  }
126  // 初始化底部
127  initFooter() {
128    const $app = document.getElementById('app')
129    const $div = document.createElement('div')
130    const $div1 = document.createElement('div')
131    const $div2 = document.createElement('div')
132    $app.classList.add('with-footer')
133    $div.classList.add('unit-detail-footer')
134    $div1.classList.add('unit-detail-footer_icon-group')
135    $div2.classList.add('unit-detail-footer_btn-group')
136    $div.appendChild($div1)
137    $div.appendChild($div2)
138    $app.appendChild($div)
139    this.$iconGroup = $div1
140    this.$btnGroup = $div2
141    document.querySelector('.widget-toTop-wrap').style.bottom = 'calc(80px + var(--SAIB))'
142  }
143  // 初始化按钮组
144  initBtn() {
145    // 客服按钮
146    if (document.getElementById('widgetSocialMediaChat')) {
147      const $service = document.createElement('div')
148      $service.classList.add('unit-detail-button-service')
149      $service.innerHTML = `<span class="iconfont icon-kefu-line"></span>`
150      this.$iconGroup.append($service)
151      $service.addEventListener('click', () => {
152        document.getElementById('widgetSocialMediaChat').querySelector('.widget-social-media-chat__server').click()
153      })
154    }
155
156    // 2b是没有购买按钮的
157    if (this.isShop && window.app.info.productDetail.status) {
158      const $buy = this.$dom.querySelector('.unit-detail-button-buy')
159      const $cart = this.$dom.querySelector('.unit-detail-button-cart')
160      this.$btnGroup.append($cart)
161      this.$btnGroup.append($buy)
162    }
163    const $comment = this.$dom.querySelector('.unit-detail-button-comment')
164    const $inquiry = this.$dom.querySelector('.unit-detail-button-inquiry')
165    this.calcInquiryBtn($inquiry)
166    if ($comment) this.$iconGroup.append($comment)
167    if ($inquiry) this.$iconGroup.append($inquiry)
168    this.calcQuoteBtn()
169  }
170  // 初始化款式参数弹窗
171  initPatternModal() {
172    if (!this.patternModal) {
173      this.patternModal = new Modal()
174      this.patternModal.init({
175        title: '',
176        body: this.getPatternGroup(),
177        size: 'lg',
178        className: "pattern-modal",
179        submitText: window.app.utilts.$t('Buy Now'),
180        cancelText: window.app.utilts.$t('Add To Cart'),
181        isCloseNoCancel: true,
182        onSubmit: () => {
183          this.utilts.setBtnLoadingStatus(this.patternModal.$submit, true)
184          this.unitButtonBuy.outerBuy()
185          setTimeout(() => {
186            this.utilts.setBtnLoadingStatus(this.patternModal.$submit, false)
187          }, 1000)
188        },
189        onCancel: () => {
190          this.utilts.setBtnLoadingStatus(this.patternModal.$cancel, true)
191          this.unitButtonCart.outerAddCart()
192          setTimeout(() => {
193            this.utilts.setBtnLoadingStatus(this.patternModal.$cancel, false)
194          }, 1000)
195        }
196      })
197    }
198  }
199  // 引用各组件的js
200  async initScript() {
201    await Promise.all([
202      new Load('/assets/js/unit/detail-album.js', 'application/javascript', {version_enabled: true}),
203      new Load('/assets/js/unit/detail-price.js', 'application/javascript', {version_enabled: true}),
204      new Load('/assets/js/unit/detail-comment-score.js', 'application/javascript', {version_enabled: true}),
205      new Load('/assets/js/unit/detail-like.js', 'application/javascript', {version_enabled: true}),
206      new Load('/assets/js/unit/detail-order-quantity.js', 'application/javascript', {version_enabled: true}),
207      new Load('/assets/js/unit/detail-pattern.js', 'application/javascript', {version_enabled: true}),
208      new Load('/assets/js/unit/detail-quantity.js', 'application/javascript', {version_enabled: true}),
209      new Load('/assets/js/unit/detail-logistics.js', 'application/javascript', {version_enabled: true}),
210      new Load('/assets/js/unit/detail-customized.js', 'application/javascript', {version_enabled: true}),
211      new Load('/assets/js/unit/detail-alert-size-chart.js', 'application/javascript', {version_enabled: true}),
212      new Load('/assets/js/unit/detail-button-buy.js', 'application/javascript', {version_enabled: true}),
213      new Load('/assets/js/unit/detail-button-cart.js', 'application/javascript', {version_enabled: true}),
214      new Load('/assets/js/unit/detail-button-comment.js', 'application/javascript', {version_enabled: true}),
215      new Load('/assets/js/unit/detail-button-inquiry.js', 'application/javascript', {version_enabled: true}),
216      new Load('/assets/js/unit/matching-product.js', 'application/javascript', {version_enabled: true}),
217      new Load('/assets/js/unit/detail-payment.js', 'application/javascript', {version_enabled: true}),
218      new Load('/assets/js/unit/detail-wholesale.js', 'application/javascript', {version_enabled: true}),
219      new Load('/assets/js/unit/detail-shipment-time.js', 'application/javascript', {version_enabled: true}),
220      new Load('/assets/js/unit/detail-button-download.js', 'application/javascript', {version_enabled: true}),
221      new Load('/assets/js/unit/detail-button-quote.js', 'application/javascript', {version_enabled: true}),
222    ])
223    this.unitAlbum = new ScriptDetailAlbum({ ...this.params, name: 'ScriptDetailAlbum' })
224    if (this.showContent.includes('score')) this.unitCommentScore = new ScriptDetailCommentScore({...this.params, name: 'ScriptDetailCommentScore'})
225    if (this.showContent.includes('like')) this.unitLike = new ScriptDetailLike({...this.params, name: 'ScriptDetailLike'})
226    if (this.showContent.includes('pattern')) this.unitPattern = new ScriptDetailPattern({...this.params, name: 'ScriptDetailPattern'})
227    if (this.showContent.includes('customized')) this.unitCustomized = new ScriptDetailCustomized({...this.params, name: 'ScriptDetailCustomized'})
228    if (this.showContent.includes('size-chart')) this.unitAlertSizeChart = new ScriptDetailAlertSizeChart({...this.params, name: 'ScriptDetailAlertSizeChart'})
229    if (this.showContent.includes('btn')) this.unitButtonComment = new ScriptDetailButtonComment({...this.params, name: 'ScriptDetailButtonComment'})
230    if (this.showContent.includes('btn')) this.unitButtonInquiry = new ScriptDetailButtonInquiry({...this.params, name: 'ScriptDetailButtonInquiry'})
231    if (this.showContent.includes('matching')) this.unitMatch = new ScriptMatchingProduct({...this.params, name: 'ScriptMatchingProduct'})
232    if (this.showContent.includes('btn')) this.unitButtonDownload = new ScriptDetailButtonDownload({...this.params, name: 'ScriptDetailButtonDownload'})
233    if (this.showContent.includes('btn') && this.show_quote_list == 1) this.unitButtonQuote = new ScriptDetailButtonQuote({...this.params, name: 'ScriptDetailButtonQuote'})
234      // 商城
235    if (this.isShop) {
236      if (this.showContent.includes('price')) this.unitPrice = new ScriptDetailPrice({...this.params, name: 'ScriptDetailPrice'})
237      if (this.showContent.includes('order')) this.unitOrderQuantity = new ScriptOrderQuantity({...this.params, name: 'ScriptOrderQuantity'})
238      if (this.showContent.includes('logistics')) this.unitLogistics = new ScriptDetailLogistics({...this.params, name: 'ScriptDetailLogistics'})
239      if (this.showContent.includes('btn')) this.unitButtonBuy = new ScriptDetailButtonBuy({...this.params, name: 'ScriptDetailButtonBuy'})
240      if (this.showContent.includes('btn')) this.unitButtonCart = new ScriptDetailButtonCart({...this.params, name: 'ScriptDetailButtonCart'})
241      if (this.showContent.includes('payment')) this.unitPayment = new ScriptDetailPayment({...this.params, name: 'ScriptDetailPayment'})
242      if (this.showContent.includes('shippingTime')) this.unitShipmentTime = new ScriptDetailShipmentTime({...this.params, name: 'ScriptDetailShipmentTime'})
243      if (this.showContent.includes('number')) this.unitQuantity = new ScriptDetailQuantity({...this.params, name: 'ScriptDetailQuantity'})
244      this.unitWholesale = new ScriptDetailWholesale({...this.params, name: 'ScriptDetailWholesale'})
245    }
246    // 类对象映射
247    this.unitScript = {
248      ScriptDetailAlbum: this.unitAlbum || null,
249      ScriptDetailCommentScore: this.unitCommentScore || null,
250      ScriptDetailLike: this.unitLike || null,
251      ScriptDetailPattern: this.unitPattern || null,
252      ScriptDetailQuantity: this.unitQuantity || null,
253      ScriptDetailCustomized: this.unitCustomized || null,
254      ScriptDetailAlertSizeChart: this.unitAlertSizeChart || null,
255      ScriptDetailButtonComment: this.unitButtonComment || null,
256      ScriptDetailButtonInquiry: this.unitButtonInquiry || null,
257      ScriptMatchingProduct: this.unitMatch || null,
258      ScriptDetailPrice: this.unitPrice || null,
259      ScriptOrderQuantity: this.unitOrderQuantity || null,
260      ScriptDetailLogistics: this.unitLogistics || null,
261      ScriptDetailButtonBuy: this.unitButtonBuy || null,
262      ScriptDetailButtonCart: this.unitButtonCart || null,
263      ScriptDetailWholesale: this.unitWholesale || null,
264      ScriptDetailPayment: this.unitPayment || null,
265      ScriptDetailShipmentTime: this.unitShipmentTime || null,
266      ScriptDetailButtonDownload: this.unitButtonDownload || null,
267      ScriptDetailButtonQuote: this.unitButtonQuote || null,
268    }
269    // 类初始化
270    for (const key in this.unitScript) {
271      if (Object.hasOwnProperty.call(this.unitScript, key) && this.unitScript[key]) {
272        this.unitScript[key].init()
273      }
274    }
275  }
276  // 渲染
277  render() {
278    // 类初始化
279    for (const key in this.unitScript) {
280      if (Object.hasOwnProperty.call(this.unitScript, key) && this.unitScript[key] && !window.app.info.noRenderList.includes(key)) {
281        this.unitScript[key].render()
282      }
283    }
284    window.app.info.noRenderList = []
285    if (this.isM) this.initMobile()
286    this.updatePatterView()
287  }
288  // 款式参数弹出框
289  renderPattern(params) {
290    // mode { 0: 没有按钮 1: 购物车和立即购买  2: 购物车 3: 立即购买 } 显示哪个按钮
291    let mode = params.mode || 1
292    mode = this.hasBuyOn ? 0 : mode
293    if (!this.isShop) mode = 0
294    switch (mode) {
295      case 0:
296        this.patternModal.$footer.style.display = 'none'
297        break
298      case 2:
299        this.patternModal.$submitWrap.style.display = 'none'
300        break
301      case 3:
302        this.patternModal.$cancelWrap.style.display = 'none'
303        break
304      default:
305        break
306    }
307    this.setBtnDisable()
308    this.updatePatterView()
309    this.patternModal.open()
310  }
311  setBtnStyleDisable(){
312    this.patternModal.$cancel.setAttribute('disabled', 'disabled')
313    this.patternModal.$cancel.classList.add('btn-disabled')
314    this.patternModal.$submit.setAttribute('disabled', 'disabled')
315    this.patternModal.$submit.classList.add('btn-disabled')
316    this.patternModal.$cancel.classList.add('d-none')
317    this.patternModal.$submit.classList.add('d-none')
318  }
319  cancelBtnDisable(){
320    this.patternModal.$submit.removeAttribute('disabled')
321    this.patternModal.$cancel.removeAttribute('disabled')
322    this.patternModal.$cancel.classList.remove('btn-disabled')
323    this.patternModal.$submit.classList.remove('btn-disabled')
324    this.patternModal.$cancel.classList.remove('d-none')
325    this.patternModal.$submit.classList.remove('d-none')
326  }
327  setBtnDisable(){
328    // console.log('movebtn')
329    if(window.app.info.productDetail&&window.app.info.productDetail.pattern&&window.app.info.productDetail.pattern.every(a=>a.selling_price===0)){
330      this.setBtnStyleDisable()
331    }
332    if(window?.app?.info?.productDetail?.is_pattern){
333      if (window.app.info.productDetail.min_price <= 0 || !window.app.info.productDetail.is_match_paramter) {
334        this.setBtnStyleDisable()
335      }else{
336        this.cancelBtnDisable()
337      }
338      if(window?.app?.info?.productDetail?.is_discount){
339        if (window?.app?.info?.productDetail?.discount_price <= 0) {
340          this.setBtnStyleDisable()
341        }else{
342          this.cancelBtnDisable()
343        }
344      }
345    }
346  }
347  updatePatterView() {
348    if (!this.patternModal) return
349    const $header = this.patternModal.$target.querySelector('.unit-detail_pattern-group-header')
350    const $img = $header.querySelector('.unit-detail_pattern-group-image img')
351    const $price = $header.querySelector('.unit-detail_pattern-group-price')
352    const $attr = $header.querySelector('.unit-detail-pattern__attr')
353    if (this.$dom.querySelector('.unit-detail-price--prices')) $price.innerHTML = this.$dom.querySelector('.unit-detail-price--prices').innerHTML
354    if (this.$dom.querySelector('.unit-detail-pattern__attr-items')) $attr.innerHTML = this.$dom.querySelector('.unit-detail-pattern__attr-items').innerHTML
355    if (window.app.info.productDetail.thumb) $img.src = window.app.info.productDetail.thumb
356  }
357  // 初始化款式参数组
358  getPatternGroup() {
359    const $div = document.createElement('div')
360    $div.classList.add('unit-detail_pattern-group')
361    $div.innerHTML = `
362      <div class="unit-detail_pattern-group-header">
363        <div class="unit-detail_pattern-group-image"><img src="${window.app.info.page.detail_thumb}" class="img-fluid" /></div>
364        <div class="unit-detail_pattern-group-info">
365          <div class="unit-detail_pattern-group-price"></div>
366          <div class="unit-detail-pattern__attr"></div>
367        </div>
368      </div>
369      <div class="unit-detail_pattern-group-body"></div>
370    `
371    const $body = $div.querySelector('.unit-detail_pattern-group-body')
372    const $pattern = this.$dom.querySelector('.unit-detail-pattern__items')
373    const $quantity = this.$dom.querySelector('.unit-detail-quantity')
374    if ($pattern) $body.appendChild($pattern)
375    if ($quantity) $body.appendChild($quantity)
376    return $div
377  }
378  // 询盘按钮特殊处理
379  calcInquiryBtn($el) {
380    const is_only_inquiry = window.app.info.productDetail.config.button_authority.length == 1 && window.app.info.productDetail.config.button_authority[0] == 'inquiry'
381    if (this.isShop && !is_only_inquiry && window.app.info.productDetail.min_price > 0) {
382      $el.querySelector('.btn').innerHTML = `<span class="iconfont icon-pinglun"></span>`
383      $el.querySelector('.btn').classList.remove('btn')
384    } else {
385      this.$iconGroup.style.width = "100%"
386      $el?.classList.add('full')
387    }
388  }
389  calcQuoteBtn() {
390    if (!(this.isShop && window.app.info.productDetail.min_price > 0) || window.app.info.productDetail.config.button_authority.length < 2) {
391      const $quote = this.$dom.querySelector('.unit-detail-button-quote')
392      if ($quote) this.$iconGroup.append($quote)
393    }
394  }
395  checkStatus() {
396    let detailPopupNode = document.getElementById('detail-popup')
397    if (this.params.templateStatus === 1 && !detailPopupNode) {
398      detailPopupNode = document.createElement('div')
399      detailPopupNode.innerHTML = `
400      <div class="unit-detail-popup__window">
401        <div class="unit-detail-popup__content">
402          <div><img src="https://img.yfisher.com/design/loading1.gif"></div>
403          <div class="progress"><div class="progress-bar"></div></div>
404          <div>${window.app.utilts.$t('The product details page is being generated. <br>You can view the generated results later.')}</div>
405          <div class="unit-detail-popup__button">
406            <a class="refresh-button btn" href="">
407              <div class="d-inline-block">
408                <span class="iconfont icon-refresh"></span> ${window.app.utilts.$t('Refresh page')}
409              </div>
410            </a>
411            <a class="goback-button btn" href="./">
412              <div class="d-inline-block">${window.app.utilts.$t('Return to Home')}</div>
413            </a>
414          </div>
415        </div>
416      </div>`
417
418      detailPopupNode.setAttribute('id', 'detail-popup')
419      detailPopupNode.className = "unit-detail-popup__window-wrap show"
420      document.body.appendChild(detailPopupNode)
421      document.body.style.overflow = 'hidden'
422
423      const self = this
424      this.loadingTimeout = setInterval(async function(){
425        const pageData = await self.getPageData()
426        if (pageData.data.content.template_status > 2) {
427          clearInterval(self.loadingTimeout)
428          self.loadingTarget = 300
429        }
430      }, 10 * 1000)
431
432      setTimeout(() => self.runProgress(), 100)
433    }
434  }
435  runProgress() {
436    const self = this
437    const $bar = document.getElementById('detail-popup').querySelector('.progress-bar')
438    let currentWidth = parseFloat($bar.style.width || 0)
439    currentWidth = currentWidth + (this.loadingTarget - currentWidth) / 150
440    $bar.style.width = (currentWidth > 100 ? 100 : currentWidth) + '%'
441    if (currentWidth >= 100) window.location.reload()
442    else setTimeout(() => self.runProgress(), 100)
443  }
444  /** 获取页面数据 */
445  getPageData() {
446    return new Promise((resolve, reject) => {
447      const params = {
448        url_path: window.app.info.page.page_url,
449        site_id: window.app.info.site.site_id,
450        language_code: window.app.info.site.language_code
451      }
452      this.utilts.request({
453        url: '/page-url/find-by-path-with-content',
454        method: 'POST',
455        params
456      }).then(res => {
457        resolve(res)
458      })
459      .catch(error => reject(error))
460    })
461  }
462}

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.