PageSourceSearch

https://www.meetion.com.bd/assets/js/design.js?v=1

js meetion.com.bd collected 2026-10-04 20:32:42 UTC 14,969 bytes, 443 lines download raw bytes

1import Local from '/src/local.js'
2import Site from '/src/site.js'
3import Page from '/src/page.js'
4import Template from '/src/template.js'
5import RenderInfo from '/src/renderInfo.js'
6import { getSiteData, getSiteNavData } from '/src/api/site.js'
7
8import Cell from '/src/package/cell.js'
9import { CustomError, lazyBGImg } from '/src/function.js'
10import DesignBox from './design-box.js'
11import { PostMessages } from './messages.js'
12import viewsData from '/dist/template/views-data.js'
13import hooks from '../../src/hooks/index.js'
14class Design {
15  /**
16   * 构造函数
17   */
18  constructor() {
19    this.ctx = {}
20    this.oldUnitTypeList = []
21    this.$loading = document.getElementById('design-loading')
22  }
23  /**
24  * 单例模式
25  */
26  static ins() {
27    if (!this.instance) return this.instance = new Design()
28    return this.instance
29  }
30
31  /**
32   * 初始化要调的各种方法
33   */
34  async init() {
35    // 初始化window.renderInfo信息1
36    window.app.info.site.site_domain = Utilts.ins().getCookie('domain')
37    window.app.info.server.visitorIp = '127.0.0.1'
38    // 初始化ctx
39    await this.initCtx()
40    // ejs
41    await new Load('/assets/plugins/ejs.min.js')
42    // 如果是小iframe,等待父窗口传入数据后再继续
43    if (this.ctx.query.render && !this.ctx.state.postCtxState) {
44      await new Promise(resolve => window.addEventListener('receiveCtxState', () => resolve()))
45    }
46    // 初始化装修
47    await this.initDesign()
48    // 把所有数据传给外部
49    if (!this.ctx.query.render) PostMessages.ins().initDesignData(this.ctx.state)
50    // 修改页面page-type 类型
51    this.updatePageType()
52    // 移除loading
53    window.addEventListener('appInitEnd', () => {
54      if (this.$loading) this.$loading.remove()
55    })
56  }
57  /** 初始化渲染 */
58  async initDesign() {
59    // 拿到本地配置
60    this.ctx.state.local = await new Local(this.ctx).init()
61    // 拿到站点数据
62    this.ctx.state.site = await new Site(this.ctx).init()
63    /** 翻译方法 */
64    this.ctx.state.$t = text => this.ctx.state.$T ? this.ctx.state.$T.$t(this.ctx.state.site.show_language_code || this.ctx.state.site.language_code, text) : text
65
66    if (this.ctx.state.site.site_id !== 0) {
67      // 拿到页面数据
68      const page = new Page(this.ctx)
69      this.ctx.state.page = await page.init()
70      // 拿到模板数据
71      this.ctx.state.template = new Template(this.ctx)
72      await this.ctx.state.template.init()
73
74      // 渲染用的数据
75      this.ctx.state.renderInfo = new RenderInfo(this.ctx)
76    }
77    if (this.ctx.state.site.site_id === 0 || this.ctx.state.page.page_type == 404) this.ctx.status = 404
78
79    window.app.info.updata(this.ctx.state.renderInfo)
80    // 获取ejs视图的数据
81    this.viewsData = viewsData
82
83    // 渲染
84    await this.render('app')
85  }
86  /**
87   * 初始化数据,如果有现成的数据,或许不用重新获取
88   */
89  async initCtx() {
90    // 初始化
91    this.ctx = window.ctx
92    // 重定向
93    this.ctx.custom = { error: CustomError }
94    // 请求接口的方法
95    this.ctx.state.request = (ctx, { url, params, method, prefix, stringify, headers }) => {
96      return Utilts.ins().request({ url, params, method, prefix, stringify, headers })
97    }
98    // 重定向的方法
99    this.ctx.state.redirect = (code, url) => {
100      if (url.indexOf('?') >= 0) url += '&'
101      else url += '?'
102      Object.keys(ctx.query).forEach(key => {
103        if (url.indexOf(`?${key}=`) >= 0 || url.indexOf(`&${key}=`) >= 0) return
104        url += `${key}=${ctx.query[key]}&`
105      })
106      url = url.substr(0, url.length - 1)
107      window.location.href = url
108    }
109    // cookies获取与设置
110    this.ctx.cookies = {
111      get(key) {
112        const res = Utilts.ins().getCookie(key)
113        return res
114      },
115      set(key, value) {
116        const res = Utilts.ins().setCookie(key, value)
117        return res
118      }
119    }
120    this.ctx.state.lazyBGImg = (ctx, thisPackage) => lazyBGImg(ctx, thisPackage)
121    this.ctx.state.$t = text => text
122  }
123  /**
124   * 渲染方法,传入id作为渲染的范围
125   * @param attr_id package id
126   * isRefres 是否是撤消,还原
127   */
128  async render(attr_id, isRefres = false) {
129    let origin_attr_id = attr_id
130    if (this.packages[attr_id]?.hook) {
131      let res = this.packages[attr_id].hook.beforeRender({attr_id, isRefres})
132      if (!res) return
133      attr_id = res.attr_id
134      isRefres = res.isRefres
135    }
136
137    const element = document.getElementById(attr_id)
138    if (!element) return
139    // 获取样式
140    await this.renderCss()
141    // HTML
142    let html = this.getEjsResult(`/layout/${attr_id.split('-')[0]}`, { package: this.packages[attr_id] })
143    attr_id !== 'app' ? element.outerHTML = html : element.innerHTML = html
144    // 运行脚本
145    this.runScript()
146    // 如果是不等于app,或者是撤消,或者是选择模块和组件时
147    if (attr_id !== 'app' || isRefres || this.ctx.state.postCtxState) {
148      await window.app.initPlug(attr_id)
149      await window.app.initScriptQueue(window.scriptQueue, 1)
150    }
151    // 初始化装修工具
152    if (!this.ctx.state.postCtxState) {
153      this.initTool(attr_id)
154    }
155    this.setAddIframEvent()
156    // const newTime = new Date().getTime()
157    // console.log(`渲染用时${newTime - oldTime}毫秒`)
158    // 如果是渲染全部页面,发送一个消息给装修
159    if (attr_id === 'app') PostMessages.ins().loadPageComplete()
160    if (this.packages[origin_attr_id]?.hook) {
161      this.packages[origin_attr_id].hook.afterRender({attr_id, isRefres})
162    }
163    if (attr_id === 'app') {
164      for (const key in hooks) {
165        if (Object.prototype.hasOwnProperty.call(hooks, key)) {
166            hooks[key]?.afterRenderApp()
167        }
168      }
169    }
170  }
171  /** 渲染样式 */
172  async renderCss() {
173    // 获取样式
174    const styleStr = this.ctx.state.template.getStyleString(this.ctx.state.template.initStyleData())
175    const $style = document.getElementById('style')
176    // 新的样式
177    $style.innerHTML = styleStr
178    const newUnitTypeList = []
179
180    const styleLink = $style.parentNode.querySelectorAll('[rel="stylesheet"]')
181    let existStyle = []
182    for (let $el of styleLink) {
183      if ($el.getAttribute('href').indexOf('unit=') > -1) {
184        existStyle = existStyle.concat($el.getAttribute('href').split('unit=')[1].split(','))
185      }
186    }
187    this.ctx.state.template.getUnitTypeList().forEach(name => {
188      if (name === 'content-list') name = 'list' // 使用list.css
189      if (this.oldUnitTypeList.includes(name)) return
190      this.oldUnitTypeList.push(name)
191      newUnitTypeList.push(name)
192    })
193
194    if (!this.ctx.state.postCtxState) {
195      if (
196        !existStyle.includes('social-media-chat') &&
197        (this.ctx.state.site.site_plugins.filter(p=>p.status === 1).length || this.ctx.state.site.site_inquiry)
198      ) {
199        newUnitTypeList.push('social-media-chat')
200      }
201      if (!existStyle.includes('cookie-settings')) {
202        newUnitTypeList.push('cookie-settings')
203      }
204    }
205
206    if (newUnitTypeList.length > 0) {
207      const newNode = document.createElement('link')
208      newNode.rel = 'stylesheet'
209      newNode.href = `/style/unit.css?unit=${newUnitTypeList.toString()}`
210      $style.parentNode.insertBefore(newNode, $style)
211    }
212  }
213  /** innerHTML,不能执行脚本,所以重新运行一下javascript */
214  runScript() {
215    const body = document.getElementById('app')
216    const scripts = body.querySelectorAll('script')
217    window.scriptQueue = [];
218    if (scripts.length === 0) return
219    scripts.forEach(script => {
220      if (script.type && /json/i.test(script.type)) {
221        return
222      }
223      // 直接 document.head.appendChild(script) 是不会生效的,需要重新创建一个
224      const newScript = document.createElement('script')
225      // 获取 inline script
226      newScript.innerHTML = script.innerHTML
227      // 存在 src 属性的话
228      const src = script.getAttribute('src')
229      if (src) newScript.setAttribute('src', src)
230      document.head.appendChild(newScript)
231      document.head.removeChild(newScript)
232    })
233  }
234  /**
235   * 获取HTML
236   */
237  /**
238 * 获取HTML
239 */
240  getEjsResult(path, data) {
241    data = this.getEjsParmes(data)
242    const ejsOptions = { client: true, delimiter: '?' }
243    const viewString = this.viewsData[path]
244    const viewCompile = window.ejs.compile(viewString, ejsOptions)
245    const viewHtml = viewCompile(data, null, (sonPath, sonData) => {
246      let thisViewString = this.viewsData[sonPath]
247      if (thisViewString) {
248        if (thisViewString.indexOf('include(') >= 1) {
249          thisViewString = this.getEjsResult(sonPath, sonData)
250        }
251        sonData = this.getEjsParmes(sonData)
252        const template = window.ejs.render(thisViewString, sonData, ejsOptions)
253        return template
254      } else {
255        console.log(sonPath)
256        console.error(`${sonPath} 未找到对应的html。`)
257      }
258    })
259    return viewHtml
260  }
261  /** 模坂引擎参数 */
262  getEjsParmes(data) {
263    return {
264      ...this.ctx.state,
265      ...data,
266      getViewsPath: path => path,
267      translate: text => text
268    }
269  }
270  /**
271   * 初始化装修工具
272   */
273  initTool(att_id) {
274    const packages = this.packages
275    if (att_id === 'app') { // 初始化所有
276      Object.keys(packages).forEach(key => {
277        packages[key].designBox = new DesignBox(packages[key])
278      })
279    } else {  // 初始化当前包
280      // 初始化自己
281      packages[att_id].designBox = new DesignBox(packages[att_id])
282      // 初始化children
283      const setDesignBox = (children) => {
284        if (children && children.length > 0) {
285          children.forEach(item => {
286            const sub_attr_id = item.attr_id
287            packages[sub_attr_id].designBox = null
288            packages[sub_attr_id].designBox = new DesignBox(packages[sub_attr_id])
289            setDesignBox(item.children)
290          })
291        }
292      }
293      setDesignBox(packages[att_id].children)
294    }
295    // 初始化富文本
296    DesignBox.initTinyMce(att_id,packages)
297  }
298  /** 初始站点样式 */
299  siteStyle(data) {
300    this.ctx.state.site.style.init(data)
301    // 获取样式
302    this.renderCss()
303  }
304  /** 实例化一个单元格 */
305  getCell(packageItem) {
306    return new Cell({
307      params: {
308        cols_col: packageItem.cols_col || 12,
309        cols_md: packageItem.cols_md || 12,
310        cols_xl: packageItem.cols_xl || 12
311      }
312    })
313  }
314
315  /** 修改装修时,添加page-type来显示当前页面的类型 */
316  updatePageType() {
317    const page = this.ctx.state.page || {}
318    const page_type = page.page_type || 'index'
319    document.querySelector('#app').setAttribute('page-type', page_type)
320  }
321
322  /** 如果是添加组件或模块时,处理点击事件和隐藏不需要的div */
323  setAddIframEvent() {
324    if (!this.ctx.state.postCtxState) return
325    const body = document.querySelector('body')
326    body.style.overflow = 'hidden'
327    const $popDiv = document.createElement('div')
328    $popDiv.style = `width:100%;height:100%;position:fixed;z-index:100;left:0;top:0;cursor: pointer;`
329    $popDiv.onclick = async (event) => {
330      event.stopPropagation()
331      PostMessages.ins().checkedPackage()
332    }
333    body.appendChild($popDiv)
334    document.querySelectorAll('[package-item="module"]').forEach(thisModule => {
335      const grid = thisModule.querySelector('[package-group="grid"]')
336      if (grid.innerHTML.trim() === '') thisModule.classList.add('d-none')
337    })
338    PostMessages.ins().postIframeIsLoading(true)
339  }
340
341  /** 重新渲染导航栏 */
342  async resetSiteNav() {
343    let siteData = await getSiteData(this.ctx, {
344      domain: window.app.info.site.site_domain
345    })
346
347    let [nav, nav_all] =  await getSiteNavData(this.ctx, {
348      site_id: this.ctx.state.site.site_id,
349      language_code: this.ctx.state.site.language_code,
350    })
351    this.ctx.state.site.nav = nav
352    this.ctx.state.site.nav_all = nav_all
353
354    if (siteData.is_show_home) {
355      this.ctx.state.site.nav.unshift({ title: this.ctx.state.$t(siteData.home_name || 'home'), href: '/', children: [], bubble: {} })
356    }
357
358    if (ctx.state.template.block['header']) {
359      const headerAttrId = ctx.state.template.block['header'].attr_id
360      this.render(headerAttrId)
361    }
362
363    for (const key in this.packages) {
364      if (Object.prototype.hasOwnProperty.call(this.packages, key)) {
365        const pkg = this.packages[key];
366        if (['list-nav', 'list_sort'].includes(pkg.type)) {
367          if (['list_sort'].includes(pkg.type)) {
368            if (this.ctx.state.page.parents.length > 1) {
369              let pData = this.ctx.state.page.parents[1]
370              if (pData.id) this.ctx.state.page.children = [this.ctx.state.site.nav.find(p => p.id === pData.id)]
371            }
372          }
373          this.render(pkg.attr_id)
374        }
375      }
376    }
377  }
378
379  _removeAnimate() {
380    // 删除动画css,显示模块
381    document.querySelectorAll('.animate').forEach(unit => {
382      unit.classList.remove('animate')
383    })
384  }
385
386  /** 更新渲染节点内容 */
387  updateRenderAttr(info) {
388    const $doms = document.querySelectorAll(`[${info.attr}]`)
389    for (let $el of $doms) {
390      if (info.value) {
391        const oldValue = $el.getAttribute(info.domAttr)
392        const oldInfo = { attr: info.attr, domAttr: info.domAttr, value: oldValue }
393        $el.setAttribute('data-attr-info', JSON.stringify(oldInfo))
394        $el.setAttribute(info.domAttr, info.value)
395      }
396    }
397  }
398  /** 还原节点内容 */
399  resetRenderAttr() {
400    const $doms = document.querySelectorAll(`[data-attr-info]`)
401    for (let $el of $doms) {
402      let oldValue = $el.getAttribute('data-attr-info')
403      if (oldValue) oldValue = JSON.parse(oldValue)
404      $el.setAttribute(oldValue.domAttr, oldValue.value)
405      $el.removeAttribute('data-attr-info')
406    }
407  }
408  /** 全局保存以后清空要还原的属性节点 */
409  clearRenderAttr() {
410    const $doms = document.querySelectorAll(`[data-attr-info]`)
411    for (let $el of $doms) {
412      $el.removeAttribute('data-attr-info')
413    }
414  }
415
416  get packages() {
417    const data = {}
418    Object.keys(this.ctx.state.template.block).forEach(blockSign => {
419      const thisBlock = this.ctx.state.template.block[blockSign]
420      if(thisBlock) data[thisBlock.attr_id] = thisBlock
421      thisBlock?.children?.forEach(thisModule => {
422        if(thisModule) data[thisModule.attr_id] = thisModule
423        thisModule?.children?.forEach(thisGrid => {
424          if(thisGrid) data[thisGrid.attr_id] = thisGrid
425          thisGrid?.children?.forEach(thisCell => {
426            if(thisCell) data[thisCell.attr_id] = thisCell
427            thisCell?.children?.forEach(thisUnit => {
428              if(thisUnit) data[thisUnit.attr_id] = thisUnit
429            })
430          })
431        })
432      })
433    })
434    return data
435  }
436
437  stringify() {
438    const obj = JSON.parse(JSON.stringify(this))
439    obj.packages = this.packages
440    return JSON.stringify(obj)
441  }
442}
443window.design = new Design()

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.