PageSourceSearch

https://www.zipper-machines.com/assets/js/unit/attr-search.js

js zipper-machines.com collected 2026-10-04 04:38:21 UTC 29,193 bytes, 735 lines download raw bytes

1/**
2 * 产品属性搜索组件
3 */
4class ScriptAttrSearch extends Script {
5  constructor(params) {
6    super(params)
7    this.style_type = +params.style_type || 1 // todo style_type
8    this.config = JSON.parse(this.utilts.htmlDecode(params.config))
9    this.colInfo = JSON.parse(this.utilts.htmlDecode(params.colInfo))
10    this.$unit = document.querySelector(`#${this.attrId}`)
11    if (!this.$unit) return
12    this.$main = this.$unit.querySelector('.unit-attr-search__items')
13    // this.$main.innerHTML = ''
14    const that = this;
15    /**
16     * 选中的checkbox
17     * @type {{}}
18     */
19    this.checkedAttrs = new Proxy({}, {
20      set(target, p, value) {
21        if (value?.id && value?.product_attr_id) {
22          const checkboxWrapNode = that.$main.querySelector(`.unit-attr-search__content--checkbox[data-checkbox_uid="${value.product_attr_id}-${value.id}"]`)
23          checkboxWrapNode && (value.checked ? checkboxWrapNode.classList.add('is_active') : checkboxWrapNode.classList.remove('is_active'))
24        }
25        return Reflect.set(target, p, value);
26      }
27    });
28    /**
29     * 选中的selectbox
30     */
31    this.selectedAttrs = {}
32  }
33
34  async init() {
35    if (this.utilts.checkDesign()) {
36      // 如果是装修时才调用
37      this.renderAttr()
38    } else {
39      // 非装修模式使用html渲染
40      if (this.style_type === 3) {
41        this.initDropdownList()
42      } else {
43        this.initListeners();
44        this.initExpandMore();
45      }
46    }
47  }
48
49  /**
50   *
51   */
52  initListeners() {
53    const that = this;
54
55    /**
56     * 点击标题展开收起内容
57     * 服务端渲染
58     * @param evt
59     */
60    const initToggleContent = (evt) => {
61      const el = evt.target;
62      if (el.classList?.contains('icon-action-left-lighter')) {
63        evt.stopPropagation();
64        const searchItem = el.closest('.unit-attr-search__item');
65        if (el._toggleContentVisible) {
66          // 客户端渲染的click事件
67          return
68        }
69        if (!searchItem) {
70          return
71        }
72        const hide_cls = 'unit-attr-search__content--hide';
73        el.classList.toggle('up');
74        if (el.classList.contains('up')) {
75          searchItem.querySelector('.unit-attr-search__content')?.classList?.add(hide_cls)
76        } else {
77          searchItem.querySelector('.unit-attr-search__content')?.classList?.remove(hide_cls)
78        }
79      }
80    }
81
82    this.$main.addEventListener('click', (evt) => {
83      const el = evt.target;
84
85      initToggleContent(evt)
86
87      // 属性筛选
88      if (el.classList?.contains('unit-attr-search__content--checkbox') || el.parentNode.classList?.contains('unit-attr-search__content--checkbox')) {
89        evt.stopPropagation();
90        const pointer = evt.target.classList.contains('unit-attr-search__content--checkbox') ? evt.target : evt.target.parentNode;
91        const checkbox = pointer.querySelector('input[type="checkbox"]');
92        const checked = !(that.checkedAttrs[checkbox.value]?.checked);
93        const checkboxInputType = parseInt(checkbox.getAttribute('data-input_type'));
94        const checkboxGroup = parseInt(checkbox.getAttribute('data-product_attr_id'));
95        const checkboxAttr = checkbox.getAttribute('data-attr_param') || 'attr';  // 属性参数
96        const checkboxDataId = parseInt(checkbox.getAttribute('data-id'));
97        that.checkedAttrs[checkbox.value] = {
98          product_attr_id: checkboxGroup,
99          attr: checkboxAttr,
100          id: parseInt(checkbox.getAttribute('data-id')),
101          input_type: checkboxInputType, // 单选/多选
102          content: checkbox.getAttribute('data-content'),
103          checked
104        };
105        if (checkboxInputType === 2 && checked) {
106          // 单选 取消同分组下的其他checkbox选中状态
107          for (let k in that.checkedAttrs) {
108            const attr = that.checkedAttrs[k];
109            if (attr.product_attr_id === checkboxGroup && attr.checked && attr.id !== checkboxDataId) {
110              attr.checked = false;
111              that.checkedAttrs[k] = attr;
112            }
113          }
114        }
115        /**
116         * @type {{[string]?: {product_attr_id: number, content: string[]}}}
117         */
118        const attr_params = {};
119        const checkedBoxes = Object.values(that.checkedAttrs).filter(box => box?.checked && box.attr === 'attr');
120        [...new Set(checkedBoxes.map(box => box.product_attr_id))].forEach(group => attr_params[group] = {
121          product_attr_id: parseInt(group),
122          content: []
123        });
124        checkedBoxes.forEach(box => attr_params[box.product_attr_id]?.content?.push(box.content));
125        window.app.info.attr_params = Object.values(attr_params);
126        const attr_detail_id = Object.values(that.checkedAttrs).filter(box => box?.checked && box.attr === 'attr_detail_id').map(box => parseInt(box.id))
127        window.app.info.attr_detail_id = Array.from(new Set(attr_detail_id))
128        // const detailAttr
129        window.app.info.renderListView();
130      }
131
132    });
133  }
134  initExpandMore() {
135    if (this.style_type !== 2) return
136    const $items = this.$unit.querySelectorAll('.unit-attr-search__item')
137
138    $items.forEach(item => {
139      let $content = item.querySelector('.unit-attr-search__content')
140      let $more = item.querySelector('.unit-attr-search__show-more-box')
141      if (!$more) {
142        $more = document.createElement('div')
143        $more.classList.add('unit-attr-search__show-more-box')
144        $more.innerHTML = `<div class="base-more">
145          <div class="show-more-inner">
146            <div class="show-more-inner__more">${Utilts.ins().$t('More')}</div>
147            <div class="show-more-inner__collect d-none">${Utilts.ins().$t('Collect')}</div>
148            <i class="show-more-inner__icon iconfont icon-action-bottom-lighter"></i>
149          </div>
150        </div>`
151        item.appendChild($more)
152      }
153
154      let $content_wrap = document.createElement('div')
155      $content_wrap.classList.add('unit-attr-search__content-wrap')
156      item.insertBefore($content_wrap, $more)
157      $content_wrap.appendChild($content)
158      let $checkbox = $content.querySelector('.unit-attr-search__content--checkbox')
159      if (!$checkbox) return
160      let maxLine = 3
161      let checkboxHeight = $checkbox.offsetHeight
162      let maxHeight = `${checkboxHeight * 3}px`
163      $content_wrap.style['max-height'] = maxHeight
164      if ($content.offsetHeight >= checkboxHeight * (maxLine+1)) {
165        $more.classList.add('show')
166      }
167      let isOpen = false
168      $more.addEventListener('click', e => {
169        const $more_btn = $more.querySelector('.show-more-inner__more')
170        const $collect_btn = $more.querySelector('.show-more-inner__collect')
171        const $arrow = $more.querySelector('.show-more-inner__icon')
172        if (isOpen) {
173          $content_wrap.style['max-height'] = maxHeight
174          $arrow.style.transform = ''
175        } else {
176          $content_wrap.style['max-height'] = ''
177          $arrow.style.transform = 'rotate(180deg)'
178        }
179        isOpen = !isOpen
180        $more_btn.classList.toggle('d-none')
181        $collect_btn.classList.toggle('d-none')
182      })
183    })
184  }
185  initDropdownList() {
186    const _this = this
187    const dropdownList = this.$main.querySelectorAll('.dropdown')
188    function preventOverflow(show) { // 防止被遮挡
189      // 给定的 CSS 选择器数组
190      const selectors = ['[package-item="unit"]', '[package-item="cell"]', '[package-item="grid"]', '[package-item="module"]', '[package-item="block"]'];
191
192      // 假设当前元素是 targetElement
193      const targetElement = _this.$unit;
194      // 从当前元素开始向上遍历父元素
195      let currentElement = targetElement;
196      while (currentElement) {
197        // 遍历选择器数组
198        selectors.forEach(selector => {
199          // 检查当前元素是否匹配选择器
200          if (currentElement.matches(selector)) {
201            // 如果匹配,提高层级
202            if (show) {
203              currentElement.style.position = 'relative';
204              currentElement.style.zIndex = '99';
205            } else {
206              currentElement.style.position = '';
207              currentElement.style.zIndex = '';
208            }
209          }
210        });
211
212        // 移动到父元素
213        currentElement = currentElement.parentElement;
214      }
215    }
216    const showMap = new Map()
217    function showDropdownMenu(dropdownMenu, show) {
218      if (show) {
219        dropdownMenu.classList.add('show')
220        showMap.set(dropdownMenu, true)
221      } else {
222        dropdownMenu.classList.remove('show')
223        showMap.set(dropdownMenu, false)
224      }
225      let showCount = 0
226      for (let value of showMap.values()) {
227        if (value) {
228          showCount++
229          break
230        }
231      }
232      preventOverflow(showCount)
233    }
234    document.addEventListener('click', function (evt) {
235      let elem = evt.target.closest('.dropdown')
236      dropdownList.forEach(function (dropdown) {
237        if (elem !== dropdown) {
238          const dropdownMenu = dropdown.querySelector('.dropdown-menu')
239          // dropdownMenu.classList.remove('show')
240          showDropdownMenu(dropdownMenu)
241        }
242      })
243    })
244    dropdownList.forEach(function (dropdown) {
245      const $tags = dropdown.querySelector('.unit-attr-search__tags')
246      const dropdownToggle = dropdown.querySelector('.dropdown-toggle')
247      const dropdownMenu = dropdown.querySelector('.dropdown-menu')
248      $tags?.addEventListener('click', (e) => {
249        let target = e.target
250        if (target.classList.contains('unit-attr-search__tag-close')) {
251          dropdownMenu.querySelector(`[data-id="${target.getAttribute('data-id')}"]`)?.click()
252        }
253      })
254      if (dropdownToggle && dropdownMenu) {
255        const input = dropdown.querySelector('input.unit-attr-search__control')
256        const popperInstance = Popper.createPopper(dropdownToggle, dropdownMenu, {
257          modifiers: [
258            {
259              name: 'offset',
260              options: {
261                offset: [0, 4],
262              },
263            }
264          ],
265        })
266
267        dropdownToggle.addEventListener('click', function (e) {
268          // dropdownMenu.classList.toggle('show')
269          showDropdownMenu(dropdownMenu, !dropdownMenu.classList.contains('show'))
270          popperInstance.update()
271        })
272        dropdownMenu.addEventListener('click', function (evt) {
273          const $el = evt.target.closest('.dropdown-item')
274          if ($el) {
275
276            evt.stopPropagation()
277            evt.preventDefault()
278            if (input) {
279              const input_type = parseInt(dropdown.getAttribute('data-input_type'))
280              if (input_type === 2) { // 单选
281                dropdownMenu.querySelectorAll('.dropdown-item').forEach(item => {
282                  if ($el !== item) {
283                    item.classList.remove('is_active')
284                  } else {
285                    $el.classList.toggle('is_active')
286                  }
287                })
288                // dropdownMenu.classList.remove('show')
289                showDropdownMenu(dropdownMenu, false)
290              } else {
291                $el.classList.toggle('is_active')
292              }
293              let item = { product_attr_id: parseInt($el.getAttribute('data-product_attr_id')), id: parseInt($el.getAttribute('data-id')), content: $el.getAttribute('title'), input_type}
294              window.app.info.pagination.page_number = 1
295              let attrs = window.app.info.attr_params
296              let detailIds = window.app.info.attr_detail_id
297
298              if ($el.classList.contains('is_active')) {
299                if (input_type === 4) detailIds.push(item.id) // 追加款式参数
300                else addAttrParams(attrs, item)  // 追加属性参数
301
302                if (input_type === 2) { // 单选
303                  _this.selectedAttrs[item.product_attr_id] = [item]
304                } else {
305                  if (!_this.selectedAttrs[item.product_attr_id]) _this.selectedAttrs[item.product_attr_id] = []
306                  _this.selectedAttrs[item.product_attr_id].push(item)
307                }
308
309              } else {
310                // 删除款式参数
311                if (input_type === 4) {
312                  window.app.info.attr_detail_id = detailIds.filter(p => p !== item.id)
313                }
314                else removeAttrParams(attrs, item) // 删除属性参数
315
316                if (input_type === 2) { // 单选
317                  _this.selectedAttrs[item.product_attr_id] = undefined
318                } else {
319                  let index = _this.selectedAttrs[item.product_attr_id].findIndex(it => item.id === it.id)
320                  _this.selectedAttrs[item.product_attr_id]?.splice(index, 1)
321                }
322              }
323              updateTags(attrs, item, dropdown, popperInstance)
324              window.app.info.renderListView()
325            }
326          }
327        })
328
329        initSearchAttr(dropdown)
330        initReset(dropdown, popperInstance)
331        initOK(dropdown)
332      }
333    })
334
335    function addAttrParams(attrs, item) {
336      const attrItem = attrs.find(p => p.product_attr_id === item.product_attr_id)
337      // 不存在时添加一个新的项
338      if (!attrItem) {
339        const params = { product_attr_id: item.product_attr_id, content: [item.content] }
340        attrs.push(params)
341      }
342      // 单选的时候只能添加一项
343      else if (item.input_type === 2) attrItem.content = [item.content]
344      // 多选时存在就不添加
345      else if (!attrItem.content.includes(item.content)) attrItem.content.push(item.content)
346    }
347    function removeAttrParams(attrs, item) {
348      const attrItem = attrs.find(p => p.product_attr_id === item.product_attr_id)
349      if (attrItem?.content.includes(item.content)) {
350        attrItem.content = attrItem.content.filter(p => p !== item.content)
351        if (attrItem.content.length === 0) {
352          window.app.info.attr_params = attrs.filter(p => p.product_attr_id !== item.product_attr_id)
353        }
354      }
355    }
356    function updateTags(attrs, item, dropdown, popperInstance) {
357      const $tags_wrap = dropdown.querySelector('.unit-attr-search__tags-wrap')
358      const $tags = dropdown.querySelector('.unit-attr-search__tags')
359      const $rest = $tags_wrap.querySelector('.unit-attr-search__tags-rest')
360      $rest.classList.add('d-none')
361      // const $input_wrap = dropdown.querySelector('.unit-attr-search__control-wrap')
362      const $input = dropdown.querySelector('input.unit-attr-search__control')
363
364      let list = _this.selectedAttrs[item.product_attr_id]
365
366      $tags.innerHTML = list?.map(it => {
367        return `<div class="unit-attr-search__tag">
368          <div class="unit-attr-search__tag-inner">
369          <span class="unit-attr-search__tag-text">${globalThis.Server.escapeHtml(it.content || 'Beige')}</span><i data-content="${it.content}" data-id="${it.id}" class="unit-attr-search__tag-close iconfont icon-action-close-2"></i>
370          </div>
371        </div>`
372      }).join('') || '';
373
374      // 强制一行 省略后续标签------------------------------------------------------------
375      const $tag_list = $tags.querySelectorAll('.unit-attr-search__tag-inner')
376      const wrapRight =  $tags_wrap.getBoundingClientRect().right
377      let hideCount = 0
378      for (let i = $tag_list.length-1; i >= 1; i--) {
379        const item = $tag_list[i];
380        const rect = item.getBoundingClientRect();
381        if (rect.right > wrapRight) {
382          item.parentNode.classList.add('d-none')
383          hideCount++
384        } else {
385          break
386        }
387      }
388      if (hideCount) {
389        $rest.querySelector('.unit-attr-search__tag-text').innerText = `+ ${hideCount}`
390          for (let i = 1; i < $tag_list.length; i++) { // 检查是否和$rest重叠
391            const item = $tag_list[i];
392            if (!item.parentNode.classList.contains('d-none') && item.getBoundingClientRect().right > $rest.getBoundingClientRect().left - 6) {
393              item.parentNode.classList.add('d-none')
394              hideCount++
395              $rest.querySelector('.unit-attr-search__tag-text').innerText = `+ ${hideCount}`
396              break
397            } else if (item.parentNode.classList.contains('d-none')){
398              break
399            }
400          }
401          $rest.classList.remove('d-none')
402      } else {
403        $rest.classList.add('d-none')
404      }
405
406      // 标签换行,更新高度 ------------------------------------------------------------
407      // if ($tags_wrap.offsetHeight) {
408      //   $input_wrap.style.height = $tags_wrap.offsetHeight + 8 + 'px'
409      // } else {
410      //   $input_wrap.style.height = ''
411      // }
412      // popperInstance.update()
413
414      if (list?.length) {
415        $input.value = ' ' // 隐藏placeholder
416        dropdown.classList.add('has-value')
417      } else {
418        $input.value = ''
419        dropdown.classList.remove('has-value')
420      }
421    }
422    function initSearchAttr(dropdown) {
423      let $search_input = dropdown.querySelector('.unit-attr-search__search-input-inner')
424      const dropdown_items = dropdown.querySelectorAll('.dropdown-item')
425      const $no_found = dropdown.querySelector('.unit-attr-search__no-found')
426      $search_input.addEventListener('input', (e) => {
427        let keyWord = e.target.value
428        let hasRes = false;
429        dropdown_items.forEach(item => {
430          if (item.getAttribute('title').includes(keyWord)) {
431            item.classList.remove('d-none')
432            hasRes = true
433          } else {
434            item.classList.add('d-none')
435          }
436        })
437        if (!hasRes) {
438          $no_found?.classList.remove('d-none')
439        } else {
440          $no_found?.classList.add('d-none')
441        }
442      })
443    }
444    function initReset(dropdown, popperInstance) {
445      const $reset_btn = dropdown.querySelector('.unit-attr-search__reset')
446      if (!$reset_btn) return
447      const dropdown_items = dropdown.querySelectorAll('.dropdown-item')
448      const product_attr_id = parseInt(dropdown_items[0]?.getAttribute('data-product_attr_id'))
449      $reset_btn.addEventListener('click', () => {
450        dropdown_items.forEach(item => {
451          item.classList.remove('is_active')
452          let itemId = parseInt(item.getAttribute('data-id'))
453          window.app.info.attr_detail_id = window.app.info.attr_detail_id.filter(p => p !== itemId)
454        })
455        window.app.info.attr_params = window.app.info.attr_params.filter(p => p.product_attr_id !== product_attr_id)
456        _this.selectedAttrs[product_attr_id] = undefined
457        updateTags(window.app.info.attr_params, { product_attr_id }, dropdown, popperInstance)
458        window.app.info.renderListView()
459      })
460    }
461    function initOK(dropdown) {
462      const $ok_btn = dropdown.querySelector('.unit-attr-search__ok')
463      if (!$ok_btn) return
464      const dropdownMenu = dropdown.querySelector('.dropdown-menu')
465      $ok_btn.addEventListener('click', () => {
466        dropdownMenu?.classList.remove('show')
467      })
468    }
469  }
470  /**
471   * 获取产品属性参数
472   */
473  getAttrList() {
474    let list = this.config.filter_attr ? window.app.info.productAttrListFilter : window.app.info.productAttrList
475    list = list
476      .filter(p => (p.input_type === 3 || p.input_type === 2 || p.input_type === 4) && p.is_show && p.status)
477      .map((p, i) => {
478        const attrs = p.attr_value.map(pp => ({
479          id: pp.id,
480          name: pp.content,
481          content: pp.content, // p.input_type === 4 ? pp.id : pp.content,
482          parent_id: p.id,
483          isSelect: false,
484          input_type: p.input_type
485        }))
486        const active = i === 0
487        return { active, id: p.id, name: p.sign || `name${i}`, input_type: p.input_type, attrs }
488      })
489    return list
490  }
491
492  renderAttr() {
493    if (this.$main.querySelector('.unit-attr-search__item')) {
494      // html输出商品属性筛选
495      return;
496    }
497    this.attrList = this.getAttrList() || []
498    this.$main.innerHTML = ''
499    if (this.attrList.length === 0) {
500      this.$unit.setAttribute('hidden', 'hidden')
501    } else if (this.$unit.hasAttribute('hidden')) {
502      this.$unit.removeAttribute('hidden')
503    }
504
505    if (this.style_type === 3) {
506      const searchInfo = new ScriptAttrSearchSelectBox(this.attrList, this.style_type, this.colInfo)
507      this.attrList.forEach(p => this.$main.append(searchInfo.getItem(p)))
508      this.initDropdownList()
509    } else {
510      const searchInfo = new ScriptAttrSearchElement(this.attrList, this.style_type)
511      this.attrList.forEach(p => this.$main.append(searchInfo.getItem(p)))
512      this.initExpandMore()
513    }
514  }
515
516
517}
518
519
520class ScriptAttrSearchElement {
521
522  constructor(attrList, style_type) {
523    this.hide = 'unit-attr-search__content--hide'
524    this.attrList = attrList
525    this.style_type = style_type
526  }
527
528  getItem(item) {
529    const $item = document.createElement('div')
530    $item.classList.add('unit-attr-search__item')
531    $item.innerHTML = ''
532
533    const $titleBox = this.getItemTitle(item)
534    const $contentBox = this.getItemContent(item)
535
536    $item.append($titleBox)
537    $item.append($contentBox)
538    return $item
539  }
540
541  getItemTitle(item) {
542    // 属性标题
543    const $titleBox = document.createElement('div')
544    $titleBox.classList.add('unit-attr-search__title')
545    $titleBox.innerHTML = ''
546    // 属性名称
547    const $spanTitle = document.createElement('span')
548    $spanTitle.classList.add('unit-attr-search__title--span')
549    const name = item.name || ''
550    $spanTitle.innerText = this.style_type === 2 ? `${name}:` : name
551    // 显示更多iconfont
552    const $spanIconfont = document.createElement('span')
553    $spanIconfont.classList.add('iconfont', 'icon-action-left-lighter')
554    if (!item.active) $spanIconfont.classList.add('up')
555    // 显示更多事件
556    $spanIconfont._toggleContentVisible = $spanIconfont.onclick = (evt) => {
557      evt.stopPropagation()
558      $spanIconfont.classList.toggle('up')
559      const $pItem = $spanIconfont.parentNode.parentNode
560      const $item = $pItem.querySelector('.unit-attr-search__content')
561      $item.classList.toggle(this.hide)
562    }
563
564    $titleBox.append($spanTitle)
565    $titleBox.append($spanIconfont)
566    return $titleBox
567  }
568
569  getItemContent(item) {
570    // 属性项
571    const $contentBox = document.createElement('div')
572    $contentBox.classList.add('unit-attr-search__content', this.hide)
573    $contentBox.innerHTML = ''
574    if (item.active) $contentBox.classList.remove(this.hide)
575    item.attrs.forEach(item => $contentBox.append(this.getItemContentCheckBox(item)))
576    return $contentBox
577  }
578
579  getItemContentCheckBox(item) {
580    // 多选项box
581    const $checkBox = document.createElement('div')
582    $checkBox.onclick = (event) => {
583      item.isSelect = !item.isSelect
584      // 单选只能选中一项
585      if (item.input_type === 2) {
586        // 当前选择值的属性值列表项
587        const $parentAttr = event.target.parentNode.parentNode
588        const groupAttr = this.attrList.find(p => p.id === item.parent_id)
589        groupAttr.attrs.forEach(p => {
590          if (p.id === item.id) {
591            p.isSelect = item.isSelect
592          } else p.isSelect = false
593        })
594        const $allAttrs = $parentAttr.querySelectorAll('.unit-attr-search__content--checkbox')
595        if ($allAttrs) $allAttrs.forEach($p => $p.classList.remove('is_active'))
596      }
597      if (item.isSelect) $checkBox.classList.add('is_active')
598      else $checkBox.classList.remove('is_active')
599      this.setAttrParams(item)
600      window.app.info.renderListView()
601    }
602    $checkBox.classList.add('unit-attr-search__content--checkbox')
603    if (item.isSelect) $checkBox.classList.add('is_active')
604    $checkBox.innerHTML = ''
605    // 多选框
606    const $inputCheckBox = document.createElement('input')
607    $inputCheckBox.type = 'checkbox'
608    // 多选择iconfont
609    const $spanIconfont = document.createElement('span')
610    $spanIconfont.classList.add('iconfont', 'icon-form-validated')
611    // label 名称
612    const $label = document.createElement('label')
613    $label.innerText = item.name || 'Beige'
614
615    $checkBox.append($inputCheckBox)
616    $checkBox.append($spanIconfont)
617    $checkBox.append($label)
618    return $checkBox
619  }
620
621  /**
622   * 设置属性查找的参数
623   * @param {Object} item
624   * @returns {Array} [{product_attr_id:'1', content:['1','2]}]
625   */
626  setAttrParams(item) {
627    window.app.info.pagination.page_number = 1
628    let attrs = window.app.info.attr_params
629    let detailIds = window.app.info.attr_detail_id
630    if (item.isSelect) {
631      if (item.input_type === 4) detailIds.push(item.id) // 追加款式参数
632      else this.addAttrParams(attrs, item)  // 追加属性参数
633    } else {
634      // 删除款式参数
635      if (item.input_type === 4) {
636        window.app.info.attr_detail_id = detailIds.filter(p => p !== item.id)
637      }
638      else this.removeAttrParams(attrs, item) // 删除属性参数
639    }
640  }
641
642  /**
643   * 添加选中的项
644   * @param {Array} attrs 选中的属性参数
645   * @param {Object} item 选中的项
646   */
647  addAttrParams(attrs, item) {
648    const attrItem = attrs.find(p => p.product_attr_id === item.parent_id)
649    // 不存在时添加一个新的项
650    if (!attrItem) {
651      const params = { product_attr_id: item.parent_id, content: [item.content] }
652      attrs.push(params)
653    }
654    // 单选的时候只能添加一项
655    else if (item.input_type === 2) attrItem.content = [item.content]
656    // 多选时存在就不添加
657    else if (!attrItem.content.includes(item.content)) attrItem.content.push(item.content)
658  }
659
660  /**
661   * 删除属性参数
662   * @param {Array} attrs 选中的属性参数
663   * @param {Object} item 选中的项
664   */
665  removeAttrParams(attrs, item) {
666    const attr = attrs.find(p => p.product_attr_id === item.parent_id)
667    if (attr.content.includes(item.content)) {
668      attr.content = attr.content.filter(p => p !== item.content)
669      if (attr.content.length === 0) {
670        window.app.info.attr_params = attrs.filter(p => p.product_attr_id !== item.parent_id)
671      }
672    }
673  }
674}
675
676class ScriptAttrSearchSelectBox {
677  constructor(attrList, style_type, colInfo) {
678    // this.hide = 'unit-attr-search__content--hide'
679    this.attrList = attrList
680    this.style_type = style_type
681    this.colInfo = colInfo
682  }
683  getItem(item) {
684    const $item = document.createElement('div')
685    $item.classList.add('unit-attr-search__item')
686    $item.classList.add(`col-${this.colInfo.cols_col}`)
687    $item.classList.add(`col-md-${this.colInfo.cols_md}`)
688    $item.classList.add(`col-xl-${this.colInfo.cols_xl}`)
689    $item.innerHTML = this.createFormElementSelect(item)
690    // this.$item = $item
691    // const $titleBox = this.getItemTitle(item)
692    // const $contentBox = this.getItemContent(item)
693
694    // $item.append($titleBox)
695    // $item.append($contentBox)
696    // this.initDropdownList()
697    return $item
698  }
699  createFormElementSelect(group) {
700    return `<div class="unit-attr-search__select-box dropdown" text-style="3" data-input_type="${group.input_type}">
701      <div class="unit-attr-search__control-wrap dropdown-toggle">
702        <input type="text" placeholder="${Utilts.ins().$t('Please select')} ${group.name}" readonly="readonly" class="unit-attr-search__control"/>
703        <div class="unit-attr-search__tags-wrap">
704          <div class="unit-attr-search__tags"></div>
705          <div class="unit-attr-search__tags unit-attr-search__tags-rest d-none">
706            <div class="unit-attr-search__tag">
707              <div class="unit-attr-search__tag-inner">
708                <span class="unit-attr-search__tag-text"></span>
709              </div>
710            </div>
711          </div>
712        </div>
713      </div>
714      <div class="unit-attr-search__dropdown-options-wrap dropdown-menu">
715        <div class="unit-attr-search__search-input">
716          <input type="text" placeholder="${Utilts.ins().$t('Search')}" class="unit-attr-search__search-input-inner"/>
717          <span class="unit-attr-search__search-input-suffix"><i class="iconfont icon-action-search-lighter"></i></span>
718        </div>
719        <ul class="unit-attr-search__select-options">
720            ${group.attrs.reduce((prev, attr) => {
721            return `${prev} <li class="unit-attr-search__select-option unit-attr-search__content--checkbox dropdown-item" title="${attr.content}" data-product_attr_id="${group.id}" data-id="${attr.id}">
722              <input type="checkbox" data-input_type="${attr.input_type}" data-attr_param="${attr.attr_param}" data-product_attr_id="${group.id}" data-id="${attr.id}" data-content="${attr.content}" value="${attr.id}">
723              <span class="iconfont icon-form-validated"></span><label>${globalThis.Server.escapeHtml(attr.content || 'Beige')}</label></li>`
724            }, '')}
725        </ul>
726        <div class="unit-attr-search__no-found d-none">No results found</div>
727        ${group.input_type === 2 ? '':
728        `<div class="unit-attr-search__footer">
729          <div class="unit-attr-search__footer-btn unit-attr-search__reset">${Utilts.ins().$t('Reset')}</div>
730          <div class="unit-attr-search__footer-btn unit-attr-search__ok">${Utilts.ins().$t('ok')}</div>
731        </div>`}
732      </div>
733    </div>`;
734  }
735}

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.