PageSourceSearch

https://www.hanbi-hotelsupplies.com/src/site.js

js hanbi-hotelsupplies.com collected 2026-10-08 16:20:15 UTC 20,932 bytes, 541 lines download raw bytes

1import { getSiteData, getSiteNavData, checkMember, getSite301, getSitePolicyList } from './api/site.js'
2import {
3  getPathLang,
4  isLanguageDomain,
5  isLanguagePath,
6  isBot,
7  CustomError,
8  checkRedirect
9} from './function.js'
10import { getPageType } from '../config/index.js'
11import Page from './page.js'
12
13class Site {
14  /**
15   * 构造函数
16   */
17  constructor(ctx) {
18    this.ctx = ctx
19    this.siteData = {}
20  }
21
22  /**
23   * 初始化
24   */
25  async init() {
26    if (this.ctx.state.postCtxState && this.ctx.state.postCtxState.site) {
27      return this.ctx.state.postCtxState.site
28    }
29    // 根据域名获取站点数据
30    const lang = getPathLang(this.ctx.path)
31    this.siteData = await getSiteData(this.ctx, { domain: this.ctx.state.local.domain, language_code: lang })
32    // 检查重定向
33    if (this.siteData.www_prefix) {
34      checkRedirect(this.ctx)
35    }
36    // 初始化模板类型
37    this.initTemplateType()
38    // 无站点id
39    if (!this.siteData.site_id) {
40      throw new CustomError(404, '无效站点')
41    }
42    // 检查装修状态
43    this.checkUpdating()
44    // 检查网站过期
45    this.checkDue()
46    // 检查页面跳转
47    await this.checkSite301()
48
49    // 处理统计代码,这是旧版,已废弃,暂时保留
50    // this.initStatusCode()
51    // 获取头部导航
52    await this.getSiteNav()
53    // 获取隐私条款开启状态
54    await this.initSiteTremsAndPolicyStatus()
55    // 获取社媒
56    // 返回处理好的数据
57    return this.siteData
58  }
59
60  /** 初始化模板类型 */
61  initTemplateType() {
62    const siteTypeToTemplateType = { 1: 'b2b', 11: 'b2b2c', 21: 'b2c', 51: 'b2b', 61: 'b2b2c' }
63    this.siteData.template_type = siteTypeToTemplateType[this.siteData.site_type || 1]
64    this.siteData.is_shop = this.siteData.template_type === 'b2b' ? false : true
65  }
66
67  /**
68   * 获取网站跳转
69   */
70  async checkSite301() {
71    if (this.ctx.query.design || !this.siteData.site_id) {
72      return
73    }
74
75    if (this.siteData.language_code === 'not_found' && this.siteData.redirect_mode === 1) {
76      this.ctx.state.redirect(301, '/')
77    }
78
79    const site301 = await getSite301(this.ctx, {
80      site_id: this.siteData.site_id
81    })
82
83    let lang_prefix = ''
84    let url_path = this.ctx.request.url
85    if (this.ctx.query.force && this.ctx.query.force === 'html') {
86      url_path = this.ctx.path
87    }
88
89    // 如果是子目录形式, 路径中去掉小语种前缀
90    if (this.siteData.domain_mode === 2 && this.siteData.lang_prefix !== '/') {
91      url_path = url_path.replace(this.siteData.lang_prefix, '')
92      lang_prefix = this.siteData.lang_prefix
93    }
94
95    let redirect_url = ''
96    if (site301.length > 0) {
97      let objExp = new RegExp(/^http/)
98      for (let i = 0; i < site301.length; i++) {
99        if (url_path === site301[i]['source_url']) {
100          if (objExp.test(site301[i]['dest_url']) === true) {
101            redirect_url = site301[i]['dest_url']
102          } else {
103            redirect_url = lang_prefix + site301[i]['dest_url']
104          }
105        }
106
107        if (this.ctx.query.force && this.ctx.query.force === 'html') {
108          if (redirect_url || site301[i]['source_url'].indexOf(url_path + '?') === 0) {
109            this.siteData.page_type = '301'
110            redirect_url = ''
111          }
112        }
113      }
114    }
115    if (redirect_url) {
116      this.ctx.request.url = '/'
117      this.ctx.state.redirect(301, redirect_url)
118    }
119  }
120
121  /**
122   * 重定向 accept-language // zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
123   * 仅首次访问站点进行自动重定向
124   * 小语种二级域名不重定向
125   * 小语种url不重定向
126   * @param ctx
127   */
128  static redirectClientLanguage(ctx) {
129    ctx.set('Vary', 'Accept-Language')
130    /**
131     * @type {string} cookie domain 最多保留2级域名
132     */
133    let domain = (ctx.state.local?.domain || '').replace(/^.*(?=\.([0-9a-z]+[.]){2}[0-9a-z]+$)/g, '')
134    const hasCookie = ctx.cookies.get('lf_language')
135    if (domain && ctx.host.indexOf(domain) >= 0) {
136      try {
137        const lf_language = ctx.state.site.language_code || 'en'
138        ctx.cookies.set('lf_language', lf_language, {
139          domain: (domain && domain[0] !== '.') ? `.${domain}` : domain,
140          path: '/',
141          httpOnly: true,
142          overwrite: true,
143          secure: false,
144          maxAge: 31536000000
145        })
146      } catch (e) {
147        console.error(e)
148      }
149    }
150    if (!ctx.state.site?.automatic_language_switching
151      || hasCookie || isLanguageDomain(ctx.state.local?.domain || '') || isLanguagePath(ctx.request.url)
152      || isBot(ctx.request.header['user-agent'] || '')
153    ) {
154      return
155    }
156    try {
157      let accept_languages = (ctx.request.header['accept-language'] || '')
158        .toLowerCase()
159        .replace(/_/g, '-')
160        .replace(/q=[0-9.]+/gi, '')
161        .replace(/[;\s]/g, '')
162        .split(',').reduce((prev, lang) => {
163          if (lang === 'zh-hk') {
164            lang = 'zh-tw'
165          }
166          prev.push(lang)
167          if (lang.indexOf('-') > -1) {
168            const langCode = lang.slice(0, lang.indexOf('-'))
169            prev.push(langCode)
170          }
171          return prev
172        }, [])
173
174      ctx.state.accept_languages = [...new Set(accept_languages)]
175
176    } catch (e) {
177      console.error(e)
178    }
179
180    let language = null
181
182    for (let lang of ctx.state.accept_languages) {
183      language = ctx.state.site.language_list.find(language => language.code === lang)
184      if (language) {
185        break;
186      }
187    }
188
189    const current_url = ctx.host + ctx.request.url
190
191    if (language && language.href
192      && current_url !== language.href.replace(/^https?:\/{2}/i, '')
193      && ctx.state.site.language_code !== language.code) {
194      console.info(`303 redirect "${current_url}" -> "${language.href}"`)
195      ctx.state.redirect(303, language.href)
196    }
197
198  }
199
200  /**
201   * 获取头部导航
202   */
203  async getSiteNav() {
204    if (!this.siteData.site_id) return
205    const isDesign = this.ctx.query.design
206    const queryLanguageCode = isDesign ? (globalThis.window.location.search.match(new RegExp("(\\?|&)language_code=([^&]*)(&|$)")) || []) : []
207
208    if (this.ctx.state.postCtxState && this.ctx.state.postCtxState.site) {
209      this.siteData.nav = this.ctx.state.postCtxState.site.nav
210    } else {
211      let [nav, nav_all] = await getSiteNavData(this.ctx, {
212        site_id: this.siteData.site_id,
213        language_code: queryLanguageCode[2] || this.siteData.language_code,
214        lang_prefix: this.siteData.lang_prefix
215      })
216      this.siteData.nav = nav
217      this.siteData.nav_all = nav_all
218    }
219    if (this.siteData.is_show_home) {
220      this.siteData.nav.unshift({ title: this.ctx.state.$T ? this.ctx.state.$T.$t(this.siteData.language_code, this.siteData.home_name) : this.siteData.home_name, href: '/', children: [], bubble: {} })
221    }
222
223    // 检查当前页面是否是重定向页面
224    if (this.ctx.query.design) return false
225
226    let redirect_url = ''
227    let url_path = this.ctx.path
228    // 如果是子目录形式, 路径中去掉小语种前缀
229    if (this.siteData.domain_mode === 2 && this.siteData.lang_prefix !== '/') {
230      url_path = this.ctx.path.replace(this.siteData.lang_prefix, '')
231    }
232    url_path = url_path.replace('.html', '')
233    const calc = (arr) => {
234      if (arr.length > 0) {
235        arr.forEach(item => {
236          if (item.href.replace('.html', '') === url_path) {
237            redirect_url = item.jump_url
238          }
239          if (item.children.length > 0) calc(item.children)
240        })
241      }
242    }
243    calc(this.siteData.nav)
244
245    if (redirect_url) {
246      if (this.ctx.query.force && this.ctx.query.force === 'html') {
247        this.siteData.page_type = '301'
248      } else {
249        this.ctx.state.redirect(303, globalThis.Server.getRinseHref(redirect_url, this.siteData))
250      }
251    }
252  }
253
254  /**
255   * 检查装修状态
256   */
257  checkUpdating() {
258    const isupdating = 'updating' === getPageType(this.ctx.path, this.siteData.site_type)
259    if (!this.ctx.query.design) {
260      let url = ''
261      if (isupdating && this.siteData.site_status !== 1) {
262        url = globalThis.Server.getRinseHref('/', this.siteData)
263      } else if (!isupdating && this.siteData.site_status === 1) {
264        url = globalThis.Server.getRinseHref('/updating.html', this.siteData)
265      }
266      if (url) {
267        if (this.ctx.query.force && this.ctx.query.force === 'html') {
268          this.siteData.page_type = '301'
269        } else {
270          this.ctx.state.redirect(303, url)
271        }
272      }
273    }
274  }
275
276  /**
277   * 检查网站过期
278   */
279  checkDue() {
280    const isdue = 'due' === getPageType(this.ctx.path, this.siteData.site_type)
281    if (!this.ctx.query.design) {
282      let url = ''
283      if (isdue && this.siteData.site_status !== 2) {
284        url = globalThis.Server.getRinseHref('/', this.siteData)
285      } else if (!isdue && this.siteData.site_status === 2) {
286        url = globalThis.Server.getRinseHref('/due.html', this.siteData)
287      }
288      if (url) {
289        if (this.ctx.query.force && this.ctx.query.force === 'html') {
290          this.siteData.page_type = '301'
291        } else {
292          this.ctx.state.redirect(303, url)
293        }
294      }
295    }
296  }
297
298  /**
299   * 检查会员功能
300   */
301  async checkMember(siteId) {
302    const memberData = await checkMember(this.ctx, { site_id: siteId || this.siteData.site_id })
303    this.siteData.member_status = memberData.status
304  }
305  /**
306   *
307   * @param {*} code
308   * @returns
309   */
310  static optimizeScript(code, ctx) {
311    if (ctx.state?.site_config?.server_config?.disable_partytown) {
312      if (ctx.state?.site_config?.ga_async) {
313        const matches = code.matchAll(/<script async src=\"https:\/\/www.googletagmanager.com\/gtag\/js\?id=([\d\-a-z]+)\">\s*<\/script>/gi)
314        ctx.state.site.ga_id = []
315        for (const match of matches) {
316          ctx.state.site.ga_id.push(match[1])
317        }
318        code = code.replace(/<script async src=\"https:\/\/www.googletagmanager.com\/gtag\/js\?id=([\d\-a-z]+)\">\s*<\/script>/gi, '')
319      }
320      return code
321    }
322    // ctx.state.site_config
323    if (code.indexOf('https://www.googletagmanager.com/') > -1) {
324
325      // fix by zhhnch 2023/09/18 ga script 标签不是partytown的加上 type="text/partytown"
326      code = code.replace(/<script(?=[^>]*src=['"][^'"]*www.googletagmanager.com[^'"]*['"][^>]*>)/gi, '<script __scrptwn__')
327        .replace(/<script([^>]*?>)(?=[^<>]*window.dataLayer)/gis, '<script __scrptwn__$1')
328        .replace(/<script([^>]*?>)(?=[^<>]*www.googletagmanager.com)/gis, '<script __scrptwn__$1')
329        .replace(/<script __scrptwn__(?!.*partytown)([^>]*)type="[^">]*"/gi, '<script __scrptwn__ $1 type="text/partytown"')
330        .replace(/<script __scrptwn__(?!.*partytown)(?!.*type=)(?=[^>]*)/gi, '<script __scrptwn__ type="text/partytown"')
331        .replace(/<script __scrptwn__\s*/gi, '<script ')
332
333    }
334
335    return code
336  }
337  static initNewStatusCode(siteData, page, ctx) {
338    const { page_url } = ctx.state.page
339    const { page_type } = page
340    const { path } = ctx
341    // 旧的列表
342    // let code_types = [
343    //   'ga',
344    //   'fbpixel',
345    //   'binguet',
346    //   'yahoodot',
347    //   'pinterestpixel',
348    //   'criteopixel',
349    //   'tiktokpixel',
350    //   'snapchatpixel',
351    //   'thirdchat',
352    //   'inquiry',
353    //   'cart',
354    //   'checkout',
355    //   'complete',
356    //   'productlist',
357    //   'productdetail',
358    //   'search',
359    //   'index',
360    //   'other'
361    // ]
362
363    // 新的列表,与后台的统计代码保持一致
364    let code_types = [
365      'ga', // GA统计
366      // 'verification', // 搜索引擎站长验证,此项不进入循环,需要单独处理
367      'inquiry', // 询盘跟踪
368      'fbpixel', // FB像素
369      'binguet', // Bing UET
370      'yahoodot', // Yahoo Dot
371      'pinterestpixel', // Pinterest像素
372      'criteopixel', // Criteo像素
373      'tiktokpixel', // TikTok像素
374      'snapchatpixel', // Snapchat像素
375      'thirdchat', // 第三方聊天
376      'meta', // 元标签
377      'style', // 样式代码
378      'customstyle' // 个性化代码
379    ]
380    let head_code = '' // 头部
381    let header_code = ''
382    let bottom_code = ''
383    let header_code_other = ''
384    let bottom_code_other = ''
385    let header_code_ai_page = ''
386    let bottom_code_ai_page = ''
387    let transfer_product_id = page.transfer_product_id
388    code_types.forEach(codeType => {
389      let curCodeType = siteData['code_' + codeType]
390      if (JSON.stringify(curCodeType) !== '{}') {
391        if (codeType === 'ga') {
392          page.effect['partytown_' + (curCodeType.hasOwnProperty('1') ? 'header' : 'bottom')] = { 'forward': ['dataLayer.push'] }
393        }
394
395        for (const position in curCodeType) {
396          curCodeType[position].forEach(item => {
397            let code = item.code
398            if (codeType === 'ga') {
399              code = ''
400              if (!item.code.includes('consent')) {
401                code += `<script>
402                  window.dataLayer = window.dataLayer || [];
403                  function gtag(){dataLayer.push(arguments);}
404                  gtag('consent', 'default', {
405                    'ad_storage': 'granted',
406                    'ad_user_data': 'granted',
407                    'ad_personalization': 'granted',
408                    'analytics_storage': 'granted'
409                  });
410                </script>`
411              }
412              code += Site.optimizeScript(item.code, ctx)
413            }
414            let isAllPage = item.page_type === 'all'
415            let isSpecifiedPage = item.page_type === page_type || (item.url_path && page_url.replace('.html', '') === item.url_path) //当前页面
416            let isNoLandingPage = item.page_type === 'no_landing_page' && page_type !== 'landing_page'
417            let isDetailPage = false
418            // 如果是详情页,区分到底是'手动创建'还是'店铺搬家',还要区分是产品详情,视频详情还是文章详情(暂时只有这三种)
419            if (item.source_apply_type !== 0 && item.page_type.includes('product_detail') && (item.page_type === page_type)) {
420              isSpecifiedPage = false
421              isDetailPage = (transfer_product_id && item.source_apply_type === 2) || (!transfer_product_id && item.source_apply_type === 1)
422            }
423            if (isAllPage) {
424              if (codeType === 'meta') {
425                head_code += code
426              } else {
427                position === '1' ? header_code_other += code : bottom_code_other += code
428              }
429              const aiPageFilter = ['fbpixel', 'binguet', 'yahoodot', 'pinterestpixel', 'criteopixel', 'tiktokpixel', 'snapchatpixel', 'thirdchat']
430              if (ctx.state.site_config.ga_filter && ctx.state.site_config.ga_filter == 'ai_page') {
431                aiPageFilter.push('ga')
432              }
433              if (!aiPageFilter.includes(codeType)) {
434                position === '1' ? header_code_ai_page += code : bottom_code_ai_page += code
435              }
436            } else if (isSpecifiedPage || isNoLandingPage || isDetailPage) {
437              if (codeType === 'meta') {
438                head_code += code
439              } else {
440                position === '1' ? header_code += code : bottom_code += code
441              }
442            }
443          });
444        }
445      }
446    })
447    let code_verification = ''
448    if (JSON.stringify(siteData.code_verification) !== '{}') {
449      siteData.code_verification[1].forEach(item => {
450        code_verification += item.code
451      })
452    }
453
454    siteData.code_verification = code_verification
455    siteData.header_code = header_code
456    siteData.head_code = head_code
457    siteData.bottom_code = bottom_code
458    siteData.header_code_other = header_code_other
459    siteData.bottom_code_other = bottom_code_other
460    siteData.header_code_ai_page = header_code_ai_page
461    siteData.bottom_code_ai_page = bottom_code_ai_page
462  }
463  /** 获取隐私条款开启状态 */
464  async initSiteTremsAndPolicyStatus() {
465    if (!this.siteData.site_id) return
466    const tremsUrl = 'terms_and_conditions'
467    const privacyUrl = 'privacy_policy'
468    const page_url = `${tremsUrl},${privacyUrl}`
469    const params = { site_id: this.siteData.site_id, page_url }
470    const list = await getSitePolicyList(this.ctx, params)
471    // const list = [{page_url: 'privacy_policy', status: 0}]
472    // const list = [{page_url: 'terms_and_conditions', status: 1}]
473    // const list = []
474    if (list.length > 0) {
475      const trems = list.find(p => p.page_url === tremsUrl) || { status: 0 }
476      const privacy = list.find(p => p.page_url === privacyUrl) || { status: 1 }
477      this.siteData.is_show_trems = trems.status === 1
478      this.siteData.is_show_privacy = privacy.status === 1
479    } else {
480      this.siteData.is_show_trems = 0
481      this.siteData.is_show_privacy = 1
482    }
483  }
484
485  /** 统计代码 */
486  // initStatusCode() {
487  //   if (!this.ctx.query.design) {
488  //     this.siteData.code_verification = this._getStatusCode(this.siteData.code_verification, 1)
489
490  //     this.siteData.code_robots = this._getStatusCode(this.siteData.code_robots, 1)
491  //     this.siteData.code_fbmessenger = this.siteData.code_fbmessenger.code || ''
492
493  //     this.siteData.code_ga_header = this._getStatusCode(this.siteData.code_ga, 1)
494  //     this.siteData.code_fbpixel_header = this._getStatusCode(this.siteData.code_fbpixel, 1)
495  //     this.siteData.code_binguet_header = this._getStatusCode(this.siteData.code_binguet, 1)
496  //     this.siteData.code_yahoodot_header = this._getStatusCode(this.siteData.code_yahoodot, 1)
497  //     this.siteData.code_pinterestpixel_header = this._getStatusCode(this.siteData.code_pinterestpixel, 1)
498  //     this.siteData.code_criteopixel_header = this._getStatusCode(this.siteData.code_criteopixel, 1)
499  //     this.siteData.code_tiktokpixel_header = this._getStatusCode(this.siteData.code_tiktokpixel, 1)
500  //     this.siteData.code_snapchatpixel_header = this._getStatusCode(this.siteData.code_snapchatpixel, 1)
501  //     this.siteData.code_thirdchat_header = this._getStatusCode(this.siteData.code_thirdchat, 1)
502  //     this.siteData.code_inquiry_header = this._getStatusCode(this.siteData.code_inquiry, 1)
503  //     this.siteData.code_cart_header = this._getStatusCode(this.siteData.code_cart, 1)
504  //     this.siteData.code_checkout_header = this._getStatusCode(this.siteData.code_checkout, 1)
505  //     this.siteData.code_complete_header = this._getStatusCode(this.siteData.code_complete, 1)
506  //     this.siteData.code_productlist_header = this._getStatusCode(this.siteData.code_productlist, 1)
507  //     this.siteData.code_productdetail_header = this._getStatusCode(this.siteData.code_productdetail, 1)
508  //     this.siteData.code_search_header = this._getStatusCode(this.siteData.code_search, 1)
509  //     this.siteData.code_other_header = this._getStatusCode(this.siteData.code_other, 1)
510  //     this.siteData.code_index_header = this._getStatusCode(this.siteData.code_index, 1)
511
512  //     this.siteData.code_ga_bottom = this._getStatusCode(this.siteData.code_ga, 2)
513  //     this.siteData.code_fbpixel_bottom = this._getStatusCode(this.siteData.code_fbpixel, 2)
514  //     this.siteData.code_binguet_bottom = this._getStatusCode(this.siteData.code_binguet, 2)
515  //     this.siteData.code_yahoodot_bottom = this._getStatusCode(this.siteData.code_yahoodot, 2)
516  //     this.siteData.code_pinterestpixel_bottom = this._getStatusCode(this.siteData.code_pinterestpixel, 2)
517  //     this.siteData.code_criteopixel_bottom = this._getStatusCode(this.siteData.code_criteopixel, 2)
518  //     this.siteData.code_tiktokpixel_bottom = this._getStatusCode(this.siteData.code_tiktokpixel, 2)
519  //     this.siteData.code_snapchatpixel_bottom = this._getStatusCode(this.siteData.code_snapchatpixel, 2)
520  //     this.siteData.code_thirdchat_bottom = this._getStatusCode(this.siteData.code_thirdchat, 2)
521  //     this.siteData.code_inquiry_bottom = this._getStatusCode(this.siteData.code_inquiry, 2)
522  //     this.siteData.code_cart_bottom = this._getStatusCode(this.siteData.code_cart, 2)
523  //     this.siteData.code_checkout_bottom = this._getStatusCode(this.siteData.code_checkout, 2)
524  //     this.siteData.code_complete_bottom = this._getStatusCode(this.siteData.code_complete, 2)
525  //     this.siteData.code_productlist_bottom = this._getStatusCode(this.siteData.code_productlist, 2)
526  //     this.siteData.code_productdetail_bottom = this._getStatusCode(this.siteData.code_productdetail, 2)
527  //     this.siteData.code_search_bottom = this._getStatusCode(this.siteData.code_search, 2)
528  //     this.siteData.code_index_bottom = this._getStatusCode(this.siteData.code_index, 2)
529  //     this.siteData.code_other_bottom = this._getStatusCode(this.siteData.code_other, 2)
530  //   }
531  // }
532
533
534  /** 获取统计代码 */
535  // _getStatusCode(codeInfo, attr) {
536  //   const info = codeInfo[attr] || []
537  //   return info.code || ""
538  // }
539}
540
541export default Site

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.