KOOTION 128MB Black USB Flash Drive 100PCS

Prices and Coupon Codes are time sensitive and may change or expire at any time! The price is subject to KOOTION Amazon Store!

$146.19
$171.99
-15%
Quantity
people are viewing this right now
/** @private {string} */ class SpzCustomAnchorScroll extends SPZ.BaseElement { static deferredMount() { return false; } constructor(element) { super(element); /** @private {Element} */ this.scrollableContainer_ = null; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.viewport_ = this.getViewport(); this.initActions_(); } setTarget(containerId, targetId) { this.containerId = '#' + containerId; this.targetId = '#' + targetId; } scrollToTarget() { const container = document.querySelector(this.containerId); const target = container.querySelector(this.targetId); const {scrollTop} = container; const eleOffsetTop = this.getOffsetTop_(target, container); this.viewport_ .interpolateScrollIntoView_( container, scrollTop, scrollTop + eleOffsetTop ); } initActions_() { this.registerAction( 'scrollToTarget', (invocation) => this.scrollToTarget(invocation?.caller) ); this.registerAction( 'setTarget', (invocation) => this.setTarget(invocation?.args?.containerId, invocation?.args?.targetId) ); } /** * @param {Element} element * @param {Element} container * @return {number} * @private */ getOffsetTop_(element, container) { if (!element./*OK*/ getClientRects().length) { return 0; } const rect = element./*OK*/ getBoundingClientRect(); if (rect.width || rect.height) { return rect.top - container./*OK*/ getBoundingClientRect().top; } return rect.top; } } SPZ.defineElement('spz-custom-anchor-scroll', SpzCustomAnchorScroll); const STRENGTHEN_TRUST_URL = "/api/strengthen_trust/settings"; class SpzCustomStrengthenTrust extends SPZ.BaseElement { constructor(element) { super(element); this.renderElement_ = null; } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback() { this.xhr_ = SPZServices.xhrFor(this.win); const renderId = this.element.getAttribute('render-id'); SPZCore.Dom.waitForChild( document.body, () => !!document.getElementById(renderId), () => { this.renderElement_ = SPZCore.Dom.scopedQuerySelector( document.body, `#${renderId}` ); if (this.renderElement_) { this.render_(); } this.registerAction('track', (invocation) => { this.track_(invocation.args); }); } ); } render_() { this.fetchData_().then((data) => { if (!data) { return; } SPZ.whenApiDefined(this.renderElement_).then((apis) => { apis?.render(data); document.querySelector('#strengthen-trust-render-1539149753700').addEventListener('click',(event)=>{ if(event.target.nodeName == 'A'){ this.track_({type: 'trust_content_click'}); } }) }); }); } track_(data = {}) { const track = window.sa && window.sa.track; if (!track) { return; } track('trust_enhancement_event', data); } parseJSON_(string) { let result = {}; try { result = JSON.parse(string); } catch (e) {} return result; } fetchData_() { return this.xhr_ .fetchJson(STRENGTHEN_TRUST_URL) .then((responseData) => { if (!responseData || !responseData.data) { return null; } const data = responseData.data; const moduleSettings = (data.module_settings || []).reduce((result, moduleSetting) => { return result.concat(Object.assign(moduleSetting, { logos: (moduleSetting.logos || []).map((item) => { return moduleSetting.logos_type == 'custom' ? this.parseJSON_(item) : item; }) })); }, []); return Object.assign(data, { module_settings: moduleSettings, isEditor: window.self !== window.top, }); }); } } SPZ.defineElement('spz-custom-strengthen-trust', SpzCustomStrengthenTrust);
Shipping
Free worldwide shipping
Free returns
Sustainably made
Secure payments

Description

Color 100 Pack-Black
Memory Storage Capacity 128 MB
Brand KOOTION
Hardware Interface USB 2.0

About this item

  • 100 pack of 128MB USB Flash Drive. ATTENTION : NOT 128 GB.
  • Plug and work, support windows 7 / 8 / 10 / Vista / XP / 2000 / ME / NT Linux and Mac OS, Compatible with USB2.0 and below
  • Good performance in digital data storing, transferring and sharing with families, friends, workmates, clients and machines. You can store music, photos, movies, designs, manuals, programs, handouts in a quick speed
  • The metal side has a 360° rotation, covering and protecting the usb end while carrying it around and turn back in any position, for the most comfortable use. Capless design with integrated loop makes it easy to attach to key chains, backpacks and etc. Mini design so that you can put it in pocket or any small space.


Product Description

KOOTION USB Flash Drives Feature:
1. Good quality and super value package deal.
2. No need software to drive and plug in use.
3. High quality original grade A chipset.
4. Light weight, foldable and practical.
5. Every Piece Tested Before Delivery.

Specifications:
Capacity: 2GB
Interface Type: support USB 2.0, backwards with USB1.0
Transfer Speed: writing: 2.5M/s-10MB/s; reading: 10M/s-22MB/s
Support PC Mode: all brands of PC, Laptop, HP, Dell, Toshiba, etc
Compatibility: Desktop, Laptop, Macintosh, Tablet, Speakers and other device with USB ports
Operating System: Windows ME/NT/ 2000/XP/7/8, Linux, Mac OS
Working Temperatures: -30º C to 70º C
Storage Temperature: -20 º C to 85ºC
Warranty and Service: 1 years warranty and support replacement and refund service

Package Content:
100 Pack x 128MB USB2.0 Flash Drives

NOTE:
All product outward appearance, the color take the material object as the picture only supply the reference. And some of the listed capacity on a flash storage device is used for formatting and other functions, thus is not available for data storage. As such, the actual available capacity for data storage is less than what is listed on the products.

const TAG = 'spz-custom-revue-util'; const DEFAULT_DELAY_TIME = 100; class SpzCustomRevueUtil extends SPZ.BaseElement { constructor(element) { super(element); this.templates_ = SPZServices.templatesForDoc(); } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); } static deferredMount() { return false; } mountCallback() { } debounceRender(el, thisEl, containerStr) { return this.smoothRender_(el, thisEl, containerStr).then(() => this.attemptToFit_(thisEl)); } smoothRender_(newEl, thisEl, containerStr) { const that = this; that.appendAsUnvisibleContainer_(newEl, thisEl); const components = newEl.querySelectorAll('[layout]'); return Promise.race([ Promise.all( Array.prototype.map.call(components, (e) => SPZ.whenDefined(e).then(() => e.whenBuilt()) ) ), SPZServices.timerFor(that.win).promise(DEFAULT_DELAY_TIME), ]).then(() => { return containerStr !== 'form_' ? thisEl.mutateElement(() => that.quickReplace(thisEl, newEl)) : thisEl.mutateElement(() => that.quickReplaceForm(thisEl, newEl)); }); } quickReplace(thisEl, newEl) { thisEl.container_ && this.toggleVisible_(thisEl.container_); this.toggleVisible_(newEl, true); thisEl.container_ && SPZCore.Dom.removeElement(thisEl.container_); thisEl.container_ = newEl; }; quickReplaceForm(thisEl, newEl) { thisEl.form_ && this.toggleVisible_(thisEl.form_); this.toggleVisible_(newEl, true); const children = thisEl.form_.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.toggleVisible_(thisEl.form_, true); thisEl.form_.appendChild(newEl); }; appendAsUnvisibleContainer_(el, thisEl) { this.toggleVisible_(el); thisEl.element.appendChild(el); } attemptToFit_(thisEl) { const fitFunc = () => { thisEl.mutateElement(this.setElementHeight_.bind(thisEl)); }; const container = thisEl.container_ || thisEl.form_; if (container) { const children = container.querySelectorAll('*:not(template)'); const spzChildren = Array.prototype.filter .call(children, SPZUtils.isSpzElement) .filter((e) => !(e.isMount && e.isMount())); spzChildren .map((e) => SPZ.whenDefined(e).then(() => e.whenMounted())) .forEach((p) => p.then(() => fitFunc())); } return fitFunc(); } setElementHeight_() { const targetHeight = (this.container_ || this.form_)?./*OK*/ scrollHeight; const height = this.element./*OK*/ offsetHeight; if (height !== targetHeight) { SPZCore.Dom.setStyles(this.element, { height: `${targetHeight}px`, }); } } toggleVisible_(el, visible = false) { if (!visible) { el.classList.add('i-spzhtml-layout-fill'); SPZCore.Dom.setStyles(el, { 'z-index': -100000, 'opacity': 0, }); } else { el.classList.remove('i-spzhtml-layout-fill'); SPZCore.Dom.setStyles(el, { 'z-index': 'auto', 'opacity': 1, }); } } setMinWidth_() { const targetWidth = this.container_?./*OK*/ scrollWidth; const width = this.element./*OK*/ offsetWidth; if (width !== targetWidth) { SPZCore.Dom.setStyles(this.element, { 'min-width': `${targetWidth}px`, }); } } triggerEvent_ = (name, data) => { const event = SPZUtils.Event.create(this.win, `${TAG}.${name}`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomRevueUtil); const TAG = 'spz-custom-revue-render'; class SPZCustomRevueRender extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); } mountCallback = () => {} render = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { if (this.element.children.length > 0) { this.element.children[0].style.display = 'none'; } this.element.appendChild(el); // const utilsEl = document.getElementById('spz_custom_revue_util'); // utilsEl && SPZ.whenApiDefined(utilsEl).then((api) => { // api.debounceRender(el, this); // }); }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueRender) const TAG = 'spz-custom-revue-star'; class SPZCustomRevueStar extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.starNum = this.element.getAttribute('starNum'); this.starTotal = this.element.getAttribute('starTotal'); this.showStarText = this.element.getAttribute('showStarText'); this.starColor = this.element.getAttribute('color'); this.interact = this.element.getAttribute('interact'); this.starSize = this.element.getAttribute('starSize') || 14; } mountCallback = () => { this.doRender_({ starTotal: this.starTotal, totalArray: Array.from({ length: Number(this.starTotal) }, (v, k) => k + 1), starNum: this.starNum, showStarText: this.showStarText, starColor: this.starColor, starSize: this.starSize }).then(() => { if (this.interact) { this.addEventListeners_(); } }); } addEventListeners_ = () => { const stars = document.querySelectorAll('.revue-star__star'); stars.forEach(star => { star.addEventListener('click', event => { const starEl = star.closest('.revue-star__star'); const starIndex = Number(starEl.dataset.index); let isHalf = event.offsetX < star.offsetWidth / 2; // rtl if (document.documentElement.getAttribute('dir') === 'rtl') { isHalf = event.offsetX > star.offsetWidth / 2; } const starValue = isHalf ? starIndex - 0.5 : starIndex; this.starClickHandler_({ value: starValue }); }); }); } renderStar = () => { const isRtl = document.documentElement.getAttribute('dir') === 'rtl'; const stars = this.element.querySelectorAll('.revue-star__star'); stars.forEach((star, i) => { const starIndex = i + 1; const starEl = star.querySelector('svg:nth-child(2)'); const isHalf = this.starNum % 1 > 0 && Math.ceil(this.starNum) === starIndex; const isSolid = starIndex <= Math.ceil(this.starNum); starEl.style.display = isSolid ? 'block' : 'none'; if (isHalf) { if (isRtl) { // RTL布局下,如果是半星,显示星星的右半边 starEl.style.clipPath = `polygon(50% 0, 100% 0, 100% 100%, 50% 100%)`; } else { // LTR布局下,如果是半星,显示星星的左半边 starEl.style.clipPath = `polygon(0 0, 50% 0, 50% 100%, 0 100%)`; } } else { starEl.style.clipPath = `polygon(0 0, 100% 0, 100% 100%, 0 100%)` } }); const showCountEle = this.element.querySelector('#revue-star-show-count'); showCountEle && SPZ.whenApiDefined(showCountEle).then((api) => { api.render({ starNum: this.starNum, starTotal: this.starTotal }); }); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, { starSize: this.starSize, ...data }, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }) .then(() => { this.starNum = data.starNum; this.renderStar(); }); } starClickHandler_ = (event) => { this.starNum = event.value; this.renderStar(); this.triggerEvent_('change', { value: event.value }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueStar) const TAG = 'spz-custom-revue-progress'; class SPZCustomRevueProgress extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.isPC = window.innerWidth > (window.breakpoint || 960); this.height = '6px'; this.color = this.element.getAttribute('color') || '#000000'; this.show_percentage = 'false'; this.show_percentage_num = 100; this.count = this.element.getAttribute('count'); this.total = this.element.getAttribute('total'); } mountCallback = () => { this.doRender_({ count: Number(this.count), total: Number(this.total), height: this.height, color: this.color, show_percentage: this.show_percentage, show_percentage_num: this.show_percentage_num }).then(() => { }); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueProgress) const TAG = 'spz-custom-revue-like'; class SPZCustomRevueLike extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.grayColor = this.element.getAttribute('gray_color') || "#BDBDBD"; this.likedColor = this.element.getAttribute('like_color') || "#FFCB44"; this.color = this.grayColor; this.count = this.element.getAttribute('count'); this.revueId = this.element.getAttribute('revue-id'); this.location = this.element.getAttribute('location'); } mountCallback = () => { const likes = sessionStorage.getItem('likes') ? JSON.parse(sessionStorage.getItem('likes')) : []; const like = likes.find(item => item.id === this.revueId); if (like) { this.color = like.like_status === 1 ? this.likedColor : this.grayColor; } // 如果location是modal,则找到相同revue-id的list的元素,拿到其count,存在list count变了,但是modal的count没变的情况 if (this.location === 'modal') { const listElement = document.querySelector(`spz-custom-revue-like[revue-id="${this.revueId}"] .revue-like-count`); if (listElement) { this.count = listElement.getAttribute('data-real-count'); } } this.doRender_({ color: this.color, count: this.count }).then(() => { this.addEventListeners_(); if(this.location === 'list') { // modal数量变更,list同步变更 document.addEventListener('like-clicked', (e) => { if (e.detail.location !== this.location && e.detail.id === this.revueId) { this.color = e.detail.like_status === 1 ? this.likedColor : this.grayColor; this.count = e.detail.count; this.element.querySelector('.revue-like__icon').querySelector('svg').setAttribute('fill', this.color); this.element.querySelector('.revue-like__icon').querySelector('svg').querySelector('path').setAttribute('fill', this.color); this.element.querySelector('.revue-like-count').innerText = this.count > 99 ? '99+' : this.count < 1 ? '' : this.count; this.element.querySelector('.revue-like-count').setAttribute('data-real-count', this.count); if(this.count > 0){ this.element.querySelector('.revue-like-count').classList.remove('hidden'); }else{ this.element.querySelector('.revue-like-count').classList.add('hidden'); } } }); } }); } addEventListeners_ = () => { const icon = this.element.querySelector('.revue-like__icon'); icon.addEventListener('click', (e) => { e.stopPropagation(); const likeStatus = this.color === this.likedColor ? 0 : 1; this.color = this.color === this.likedColor ? this.grayColor : this.likedColor; this.count = likeStatus === 1 ? parseInt(this.count) + 1 : parseInt(this.count) - 1; icon.querySelector('svg').setAttribute('fill', this.color); icon.querySelector('svg').querySelector('path').setAttribute('fill', this.color); this.element.querySelector('.revue-like-count').innerText = this.count > 99 ? '99+' : this.count < 1 ? '' : this.count; this.element.querySelector('.revue-like-count').setAttribute('data-real-count', this.count); if(this.count > 0){ this.element.querySelector('.revue-like-count').classList.remove('hidden'); }else{ this.element.querySelector('.revue-like-count').classList.add('hidden'); } this.postLike(likeStatus); if (this.location === 'modal') { const clickedEvent = new CustomEvent('like-clicked', { detail: { id: this.revueId, like_status: likeStatus, count: this.count, location: this.location } }); document.dispatchEvent(clickedEvent); } }); } setLikeToStorage = (likeToStore) => { if (typeof (Storage) !== 'function') return; const likesInStore = sessionStorage.getItem('likes') ? JSON.parse(sessionStorage.getItem('likes')) : []; const reviewIndex = likesInStore.findIndex(item => item.id === likeToStore.id); if (reviewIndex !== -1) { likesInStore[reviewIndex].like_status = likeToStore.like_status; likesInStore[reviewIndex].count = likeToStore.count; } else { likesInStore.push(likeToStore); } sessionStorage.setItem('likes', JSON.stringify(likesInStore)); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } postLike = (likeStatus) => { fetch('/api/comment/like', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ id: this.revueId, status: likeStatus }) }).then((res) => { if (res.status === 200) { this.setLikeToStorage({ id: this.revueId, like_status: likeStatus, count: this.count }); } }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueLike) const TAG = 'spz-custom-revue-media'; class SPZCustomRevueMedia extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.imgCover = this.element.getAttribute('img-cover') ?? false; this.pc_layout = this.element.getAttribute('pc-layout') ?? ''; // data-images 格式为 xxxx.png?width=1&height=1,xxxx.png?width=1&height=1 const images = this.element.getAttribute('data-images').split(',') || []; const parsedImages = images.map(image => { return this.mediaParse_(image); }); this.images = parsedImages; this.isPC = window.innerWidth > 960; } mountCallback = () => { this.doRender_({ images: this.images, isPC: this.isPC, imgCover: this.imgCover, pc_layout: this.pc_layout }).then(() => { this.addEventListeners_(); }); } addEventListeners_ = () => { const images = this.element.querySelectorAll('.revue-image-item'); images.forEach((image, index) => { image.addEventListener('click', () => { const carousel = document.querySelector('#revue-image-carousel-render'); carousel && SPZ.whenApiDefined(carousel).then((api) => { const width = this.isPC ? 460 : window.innerWidth * 0.9; const height = this.isPC ? 630 : 500; api.render({ images: this.images, index: index, width: width, height: height }); }); }); }); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } mediaParse_ = function (url) { var result = {}; try { url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) { try { result[key] = decodeURIComponent(value); } catch (e) { result[key] = value; } }); result.preview_image = url.split('?')[0]; } catch (e) {}; return result; } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueMedia) const TAG = 'spz-custom-revue-sort'; class SPZCustomRevueSort extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.isPC = window.innerWidth > 960; this.width = this.isPC ? `${this.element.getAttribute('width') || 150}px` : '100%'; this.randomStr = Math.random().toString(36).substr(2); this.sectionId = this.element.getAttribute('section-id') || '1731390051215'; this.prefix = this.element.getAttribute('prefix'); } mountCallback = () => { const data = { width: this.width, randomStr: this.randomStr }; this.doRender_(data).then(() => { let revueSortListRender = this.isPC ? this.element.querySelector(`#${this.prefix}-revue-sort-list-render-${this.sectionId}`) : this.element.querySelector(`#${this.prefix}-revue-sort-dropdown-render-${this.sectionId}`); revueSortListRender && SPZ.whenApiDefined(revueSortListRender).then((api) => { api.render(data).then(() => { if (this.isPC) { this.addEventListenersForPC_(); } else { this.addEventListenersForMobile_(); } }); }); }); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } addEventListenersForPC_ = () => { const revueSelectList = this.element.querySelector('.revue_select_list'); const revueSelectItem = this.element.querySelectorAll('.revue_select_item'); const revueSelectSortIcon = this.element.querySelector(`#${this.prefix}-revue_select_sort_icon-${this.sectionId}`); revueSelectItem.forEach(item => { item.addEventListener('click', () => { const sort = item.getAttribute('data-sort'); const direction = item.getAttribute('data-direction'); this.triggerEvent_('sort', { sort, direction }); this.element.querySelector('.revue_select_label').innerText = item.innerText; revueSelectList.classList.remove('revue_select_list_active'); const revueChecked = this.element.querySelector(`#${this.prefix}-revue_checked`); revueChecked && SPZCore.Dom.removeElement(revueChecked); const revueCheckedClone = revueChecked.cloneNode(true); item.appendChild(revueCheckedClone); const pcDropdownEle = document.querySelector(`#${this.prefix}-revue-sort-pc-dropdown-${this.sectionId}`); if (!revueSelectSortIcon.classList.contains('up_icon')) { return; } revueSelectSortIcon.classList.remove('up_icon'); SPZ.whenApiDefined(pcDropdownEle).then((api) => { api.close(); }); }); }); window.addEventListener('scroll', (e) => { if (!revueSelectSortIcon || !revueSelectSortIcon.classList.contains('up_icon')) { return; } revueSelectSortIcon.classList.remove('up_icon'); SPZ.whenApiDefined(pcDropdownEle).then((api) => { api.close(); }); }); } addEventListenersForMobile_ = () => { const revueSortDropdownRender = document.querySelector(`#${this.prefix}-revue-sort-dropdown-render-${this.sectionId}`); revueSortDropdownRender && SPZ.whenApiDefined(revueSortDropdownRender).then(async (api) => { await api.render(); const revueSortDropdownItem = document.querySelectorAll(`#${this.prefix}-revue-sort-dropdown-${this.sectionId} .revue_sort_dropdown_item`); revueSortDropdownItem.forEach(item => { item.addEventListener('click', () => { const sort = item.getAttribute('data-sort'); const direction = item.getAttribute('data-direction'); revueSortDropdownItem.forEach((_item)=>{_item.classList.remove('selected')}) item.classList.add('selected'); // 抛出事件 this.triggerEvent_('sort', { sort, direction }); // 移除revue_checked元素,复制一个新的到当前选中的元素 const revueChecked = document.querySelector(`#${this.prefix}-revue-sort-dropdown-${this.sectionId} #${this.prefix}-revue_checked`); revueChecked && SPZCore.Dom.removeElement(revueChecked); const revueCheckedClone = revueChecked.cloneNode(true); item.appendChild(revueCheckedClone); const mDropdownEle = document.querySelector(`#${this.prefix}-revue-sort-dropdown-${this.sectionId}`); SPZ.whenApiDefined(mDropdownEle).then((api) => { api.close(); }); }); }); }) } } SPZ.defineElement(TAG, SPZCustomRevueSort) const TAG = 'spz-custom-revue-type'; class SPZCustomRevueType extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.isPC = window.innerWidth > 960; this.width = this.isPC ? `${this.element.getAttribute('width') || 150}px` : '100%'; this.randomStr = Math.random().toString(36).substr(2); this.sectionId = this.element.getAttribute('section-id') || '1731390051215'; this.prefix = this.element.getAttribute('prefix'); } mountCallback = () => { } render = (data) => { const renderData = { ...data, width: this.width, randomStr: this.randomStr }; return this.templates_ .findAndRenderTemplate(this.element, renderData, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }).then(() => { let revueTypeListRender = this.isPC ? this.element.querySelector(`#${this.prefix}-revue-type-list-render-${this.sectionId}`) : this.element.querySelector(`#${this.prefix}-revue-type-dropdown-render-${this.sectionId}`); revueTypeListRender && SPZ.whenApiDefined(revueTypeListRender).then((api) => { api.render(renderData).then(() => { if (this.isPC) { this.addEventListenersForPC_(); } else { this.addEventListenersForMobile_(); } }); }); }); } addEventListenersForPC_ = () => { const revueSelectList = this.element.querySelector('.revue_select_list'); const revueSelectItem = this.element.querySelectorAll('.revue_select_item'); const revueSelectTypeIcon = this.element.querySelector(`#${this.prefix}-revue_select_type_icon-${this.sectionId}`); revueSelectItem.forEach(item => { item.addEventListener('click', () => { const type = item.getAttribute('data-type'); const direction = item.getAttribute('data-direction'); this.triggerEvent_('type', { type, direction }); this.element.querySelector('.revue_select_label').innerText = item.innerText; revueSelectList.classList.remove('revue_select_list_active'); const revueChecked = this.element.querySelector(`#${this.prefix}-revue_checked`); revueChecked && SPZCore.Dom.removeElement(revueChecked); const revueCheckedClone = revueChecked.cloneNode(true); item.appendChild(revueCheckedClone); if (!revueSelectTypeIcon.classList.contains('up_icon')) { return; } const pcDropdownEle = this.element.querySelector(`#${this.prefix}-revue-type-pc-dropdown-${this.sectionId}`); revueSelectTypeIcon.classList.remove('up_icon'); SPZ.whenApiDefined(pcDropdownEle).then((api) => { api.close(); }); }); }); window.addEventListener('scroll', (e) => { if (!revueSelectTypeIcon.classList.contains('up_icon')) { return; } revueSelectTypeIcon.classList.remove('up_icon'); SPZ.whenApiDefined(pcDropdownEle).then((api) => { api.close(); }); }); } addEventListenersForMobile_ = () => { const revueTypeDropdownItem = this.element.querySelectorAll(`#${this.prefix}-revue-type-dropdown-${this.sectionId} .revue_type_dropdown_item`); revueTypeDropdownItem.forEach(item => { item.addEventListener('click', () => { const type = item.getAttribute('data-type'); const direction = item.getAttribute('data-direction'); revueTypeDropdownItem.forEach((_item)=>{_item.classList.remove('selected')}) item.classList.add('selected'); // 抛出事件 this.triggerEvent_('type', { type, direction }); // 移除revue_checked元素,复制一个新的到当前选中的元素 const revueChecked = this.element.querySelector(`#${this.prefix}-revue-type-dropdown-${this.sectionId} #${this.prefix}-revue_checked`); revueChecked && SPZCore.Dom.removeElement(revueChecked); const revueCheckedClone = revueChecked.cloneNode(true); item.appendChild(revueCheckedClone); const mDropdownEle = this.element.querySelector(`#${this.prefix}-revue-type-dropdown-${this.sectionId}`); SPZ.whenApiDefined(mDropdownEle).then((api) => { api.close(); }); }); }); } } SPZ.defineElement(TAG, SPZCustomRevueType) const TAG = 'spz-custom-revue-pagination'; class SPZCustomRevuePagination extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.isPC = window.innerWidth > (window.breakpoint || 960); this.numItems = this.numItems(); this.pageSize = this.pageSize(); } mountCallback = () => { this.doRender_({ numPages: this.numPages(), pageNum: this.currentPageNumber(), useCallback: true }).then(() => { }); } currentPageNumber() { let pageNum = this.element.getAttribute('page-num'); if (pageNum) return parseInt(pageNum); } numPages() { return Math.ceil(this.numItems / this.pageSize); } numItems() { return parseInt(this.element.getAttribute('num-items')); } pageSize() { return parseInt(this.element.getAttribute('page-size')) || 10; } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, data, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevuePagination) const TAG = 'spz-custom-revue-product'; class SpzCustomRevueProduct extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback = () => { this.section_id = this.element.getAttribute('section-id'); this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); const url = new URL(window.location.href); this.isPC = window.innerWidth > (window.breakpoint || 960); this.nodata = false; this.firstRender = true; this.commentConfig = {}; this.commentSummary = {}; this.commentList = {}; this.panelId = 'all'; this.sort = 'created_at'; this.direction = 'desc'; this.pageNum = 1; this.pageSize = +window.reviewProductSettings[this.section_id].page_limit; this.pc_layout = window.reviewProductSettings[this.section_id].pc_layout; this.star_least = +window.reviewProductSettings[this.section_id].star_least; this.only_media = window.reviewProductSettings[this.section_id].only_media; this.product_id = window.SHOPLAZZA.meta.page.resource_id; this.isProductPage = '1' == 1; this.isCollectionPage = '1' == 2; this.isCartPage = '1' == 13; this.review_insufficient = window.reviewProductSettings[this.section_id].review_insufficient; // 评论不足类型 this.mini_quantity = window.reviewProductSettings[this.section_id].mini_quantity; // 评论少于一定数量 this.actions = window.reviewProductSettings[this.section_id].actions; // 评论处理方式 this.only_media = window.reviewProductSettings[this.section_id].only_media; // 只显示有图片的评论 this.only_featured = window.reviewProductSettings[this.section_id].only_featured ?? false; // 只显示精选评论 this.display_product_link = window.reviewProductSettings[this.section_id].display_product_link ?? false; // 是否显示商品链接 this.m_loading_type = window.reviewProductSettings[this.section_id].m_loading_type; // 移动端加载方式 this.m_modal_page_limit = window.reviewProductSettings[this.section_id].m_modal_page_limit; // 移动端弹窗加载限制 this.hide_review_section = window.reviewProductSettings[this.section_id].hide_review_section; // 无数据是否隐藏评论组件 this.accent_color = window.reviewProductSettings[this.section_id].accent_color; // 主题色 } mountCallback = () => { this.templates_ .findAndRenderTemplate(this.element, { isPC: this.isPC }, null) .then((el) => { this.element.appendChild(el); this.renderPage(); }) } fetchCommentConfig_ = async () => { const response = await fetch('/api/comment-config'); return response.json(); } fetchCommentSummary_ = async(data) => { const response = await fetch(`/api/v1/comments/summary`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data) }); return response.json(); } fetchCommentList_ = async(data) => { // const response = await fetch(`/api/comment/list?show_product=1&star_least=${data.star_least}&onlyimg=${data.onlyimg}&limit=${data.limit}&offset=${data.offset}&sort_by=${data.sort_by || 'created_at'}&product_id=${data.productId}&status=1&sort_direction=${data.sort_direction || 'desc'}&show_reply=${data.show_reply}`); const response = await fetch('/api/v1/comments', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data) }); return response.json(); } fetchThemeConfig_ = async(themeId) => { const response = await fetch(`/api/comment/theme-config?theme_id=${themeId}`); return response.json(); } getCommentConfig = () => { return this.fetchCommentConfig_() } getCommentSummary = (data = {}) => { const fetchData = { star_least: this.star_least, product_ids: this.isProductPage ? '5a255568-a705-44f1-b624-5bf6f99f7aa6' : this.isCartPage ? '' : '', collection_id: this.isCollectionPage ? '' : '', filter_type: this.isProductPage ? 'product' : this.isCollectionPage ? 'collection' : 'store', fill_min_threshold: this.review_insufficient === 'less_than' ? this.mini_quantity : undefined, fill_strategy: this.actions === 'all_product' ? 'store' : '', only_media: this.only_media ? this.only_media : this.panelId !== 'all', only_featured: this.only_featured, ...data, } return this.fetchCommentSummary_(fetchData) } getCommentList = (data = {}) => { const fetchData = { show_product: true, filter_type: (this.isProductPage || this.isCartPage) ? 'product' : this.isCollectionPage ? 'collection' : 'store', star_least: this.star_least, show_reply: true, limit: this.pageSize, offset: (this.pageNum - 1) * this.pageSize, only_media: this.only_media ? this.only_media : this.panelId !== 'all', sort_by: this.sort, sort_direction: this.direction, product_ids: this.isProductPage ? '5a255568-a705-44f1-b624-5bf6f99f7aa6' : this.isCartPage ? '' : '', collection_id: this.isCollectionPage ? '' : '', only_featured: this.only_featured, fill_strategy: this.actions === 'all_product' ? 'store' : '', fill_min_threshold: this.review_insufficient === 'less_than' ? this.mini_quantity : undefined, ...data, } return this.fetchCommentList_(fetchData) } getPageData = () => { return Promise.all([ this.getCommentConfig(), this.getCommentSummary(), this.getCommentList() ]) } renderPage = async () => { const [commentConfigRes, commentSummaryRes, commentListRes] = await this.getPageData(); let commentConfigData = commentConfigRes.data || {}; let commentSummaryData = commentSummaryRes.data || {}; let commentListData = commentListRes.data || []; this.commentConfig = commentConfigData; this.commentSummary = commentSummaryData; this.commentList = commentListData; this.accent_color = this.accent_color || this.commentConfig.star_color; let lessThanCount = 1; // 评论不足逻辑 if(this.actions === "hide") { // 不展示评论组件 if(this.review_insufficient === 'less_than') { lessThanCount = this.mini_quantity; // 无评论或少于一定数量 } } else if(this.actions === "all_product") { lessThanCount = 1; } if(commentListData.count < lessThanCount) { // 是否隐藏评论组件 if(this.hide_review_section) { this.renderNoData(); return null; } else { this.renderRevueEmpty(); } this.nodata = true; } window.addEventListener('resize', SPZCore.Types.throttle(window, this.onResize, 300)); this.renderPageData([this.commentConfig, this.commentSummary, this.commentList]); } onResize = () => { if(this.nodata) { return; } // 判断是否需要重新渲染 if((this.isPC && window.innerWidth > (window.breakpoint || 960)) || (!this.isPC && window.innerWidth < (window.breakpoint || 960))) { return; } this.isPC = window.innerWidth > (window.breakpoint || 960); this.panelId = 'all'; this.sort = 'created_at'; this.direction = 'desc'; this.pageNum = 1; this.templates_ .findAndRenderTemplate(this.element, { isPC: this.isPC }, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); this.renderPageData([this.commentConfig, this.commentSummary, this.commentList]); }) } renderPageData = (data) => { const [commentConfigData, commentSummaryData, commentListData] = data; // 渲染头部 this.renderHeader_({ starData: commentSummaryData, listData: commentListData, comment_avg_star: commentSummaryData.comment_avg_star, comment_count: commentSummaryData.comment_count, }); // 有评论逻辑 this.renderStarCounts(commentSummaryData); if(this.isPC && this.pc_layout === 'single_column') { this.renderCommentTab({ listData: commentListData, isPC: this.isPC, }, `revue-tab-${this.section_id}`); } else { this.renderList_({ listData: commentListData, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name, isPC: this.isPC, star_color: this.accent_color, }); } } renderNoData = () => { const sectionEle = document.querySelector(`#revue-product-compo`); if (sectionEle) { sectionEle.setAttribute('hidden', 'true'); } if(window.top === window.self) { // c端不渲染 return; } // b端渲染 const noDataPlaceholder = document.querySelector(`#revue_no_data_placeholder_${this.section_id}`); if(noDataPlaceholder) { SPZ.whenApiDefined(noDataPlaceholder).then(async (api) => { await api.render(); }); } } renderRevueEmpty = (section) => { const emptyEle = document.querySelector(`#revue_empty-${this.section_id}`); const writeReviewBtn = document.querySelector(`#revue_write_review_btn_single`); if (emptyEle) { emptyEle.classList.remove('hidden'); } if (writeReviewBtn) { writeReviewBtn.classList.remove('hidden'); } const skeletonEle = document.querySelector('#revue_skeleton'); if (skeletonEle) { skeletonEle.classList.add('hidden'); } } renderHeader_ = (data) => { const headerEle = document.querySelector(`#app-review-revue-header-${this.section_id}`); if (headerEle) { SPZ.whenApiDefined(headerEle).then(async (api) => { api.render({ ...data, star_color: this.accent_color, isPC: this.isPC, }); }); } } renderStarCounts = (data, eleId = `revue-summary-${this.section_id}`) => { const ndata = { ...this.commentSummary, star_color: this.accent_color, isPC: this.isPC, ...data, } const summaryEle = document.querySelector(`#${eleId}`); if (summaryEle) { SPZ.whenApiDefined(summaryEle).then((api) => { api.render({ ...ndata, }); }); } } renderCommentTab = (data, eleId) => { const elementId = eleId || `revue-tab-${this.section_id}`; const ndata = { listData: this.commentList, isPC: this.isPC, ...data } const tabEle = document.querySelector(`#${elementId}`); let listId; if (tabEle) { SPZ.whenApiDefined(tabEle).then(async (api) => { await api.render({ ...ndata, // suffix: "list", }); if(eleId) { listId = `revue-comment-list-${this.section_id}_tab`; } this.renderList_({ ...ndata, // suffix: "list", }, listId); }); } } renderList_ = (data, eleId) => { const listEle = document.querySelector(`#revue-comment-list`); if (listEle && !eleId) { SPZ.whenApiDefined(listEle).then(async (api) => { await api.render({ ...data, // suffix: "list", pageSize: this.pageSize, hasmore: data.listData.has_more, }) let nlist = data.listData.list.map(item => { return { ...item, config: this.commentConfig, star_color: this.accent_color, shop_name: window.SHOPLAZZA.shop.shop_name, current_panel: this.panelId, pageNum: this.pageNum, suffix: data.suffix, show_link: this.display_product_link, } }) let hasmore = data.listData.has_more; if(!this.isPC && this.m_loading_type === 'modal') { nlist = nlist.slice(0, this.m_modal_page_limit); hasmore = true; } api.renderList({ ...data, list: nlist, count: this.panelId === 'all' ? data.listData.count : data.listData.image_count, // suffix: "list", hasmore: hasmore, pageSize: this.pageSize }) }) return; } const viewallListEle = document.querySelector(`#${eleId}`); if (viewallListEle) { SPZ.whenApiDefined(viewallListEle).then(async (api) => { await api.render({ ...data, pageSize: this.pageSize, hasmore: data.listData.has_more, }); let nlist = data.listData.list.map(item => { return { ...item, config: this.commentConfig, star_color: this.accent_color, shop_name: window.SHOPLAZZA.shop.shop_name, current_panel: this.panelId, pageNum: this.pageNum, suffix: data.suffix, show_link: this.display_product_link, } }) api.renderList({ ...data, list: nlist, count: this.panelId === 'all' ? data.listData.count : data.listData.image_count, hasmore: data.listData.has_more, pageSize: this.pageSize, }) }); } } renderCommentList = (data, eleId = 'revue-comment-list', renderType = 'list', redo = false) => { const listEle = document.querySelector(`#${eleId}`); if (listEle) { SPZ.whenApiDefined(listEle).then((api) => { let nlist = data.listData.list.map(item => { return { ...item, config: this.commentConfig, star_color: this.accent_color, shop_name: window.SHOPLAZZA.shop.shop_name, current_panel: this.panelId, pageNum: this.pageNum, hasmore: data.listData.has_more, show_link: this.display_product_link, // suffix: data.suffix, } }) if(!this.isPC && this.m_loading_type === 'modal' && renderType === 'list') { nlist = nlist.slice(0, this.m_modal_page_limit); } api.renderList({ count: this.panelId === 'all' ? data.listData.count : data.listData.image_count, list: nlist, // suffix: "list", hasmore: data.listData.has_more, pageSize: this.pageSize }, redo); }); return; } } renderByScrollPagination = async (eleId, renderType) => { this.pageNum = this.pageNum + 1; const params = {} const res = await this.getCommentList(params); this.renderCommentList({ listData: res.data, }, eleId, renderType, false); } setupAction_ = () => { this.registerAction('renderTabChangeList', async (invocation) => { // 兼容 ljs-tab 首次加载会触发 tabchange 事件 if(this.firstRender) { this.firstRender = false; return; } const panelId = invocation.args.data.panelId; const { eleId, renderType } = invocation.args; this.panelId = panelId; this.pageNum = 1; this.modalHasMore = true; const params = { // only_media: panelId !== 'all', } const res = await this.getCommentList(params); this.renderCommentList({ listData: res.data, }, eleId, renderType, true); }); this.registerAction('renderTypeChangeList', async (invocation) => { const { type } = invocation.args.data; const { eleId, renderType } = invocation.args; this.panelId = type; this.pageNum = 1; this.modalHasMore = true; const params = { // only_media: type !== 'all', } const res = await this.getCommentList(params); this.renderCommentList({ listData: res.data, }, eleId, renderType, true); }); this.registerAction('renderSortedList', async(invocation) => { const { sort, direction } = invocation.args.data; const eleId = invocation.args.eleId; const renderType = invocation.args.renderType; this.sort = sort; this.direction = direction; this.pageNum = 1; this.modalHasMore = true; const params = { sort_by: sort, sort_direction: direction, } const res = await this.getCommentList(params); this.renderCommentList({ listData: res.data, }, eleId, renderType, true); }); this.registerAction('renderByPagination', async(invocation) => { const { pageNum, eleId, renderType } = invocation.args; this.pageNum = pageNum; const params = {} const res = await this.getCommentList(params); this.renderCommentList({ listData: res.data, }, `revue-comment-list-${this.section_id}_tab`, 'tab', true); const tabsEle = document.querySelector('#revue-product-compo'); if (tabsEle) { tabsEle.scrollIntoView({ behavior: 'smooth' }); } }); this.registerAction('renderByViewMore', async(invocation) => { const { eleId, renderType } = invocation.args; this.pageNum = this.pageNum + 1; const params = {} const res = await this.getCommentList(params); this.renderCommentList({ listData: res.data, }, eleId, renderType, false); }); this.registerAction('refresh', async(invocation) => { this.panelId = 'all'; this.sort = 'created_at'; this.direction = 'desc'; this.pageNum = 1; this.templates_ .findAndRenderTemplate(this.element, { isPC: this.isPC }, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); this.renderPage(); }); const productEle = document.querySelector(`#revue-viewall-modal-comp`); if (productEle) { SPZ.whenApiDefined(productEle).then(async (api) => { api.refresh(); }); } }); } } SPZ.defineElement(TAG, SpzCustomRevueProduct) (function() { const TAG = 'spz-custom-new-revue'; class SpzCustomNewRevue extends SPZ.BaseElement { constructor(element) { super(element); this.config_ = null; this.loading_ = false; this.accent_color = this.element.getAttribute('accent-color'); this.sectionId = this.element.getAttribute('section-id'); this.prefix = this.element.getAttribute('prefix'); } buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.form_ = SPZCore.Dom.scopedQuerySelector( this.element, 'form' ); this.hasShowLengthInputs_ = SPZCore.Dom.scopedQuerySelectorAll( this.form_, '[showlength]' ); [...this.hasShowLengthInputs_].forEach(item => { const countRecordDom = SPZCore.Dom.scopedQuerySelector( this.form_, `#${item.id} ~ div[type="count-record"]` ); if (!countRecordDom) { console.error(`Cannot find count record DOM element for input ${item.id}`); return; } item.addEventListener('input', (e) => { countRecordDom.innerText = `${e.target.value.length}/3000`; }); }); this.setupAction_(); this.getRevueConfigData_(); } setupAction_() { this.registerAction('submitForm', async(invocation) => { if (this.loading_) { return; } this.loading_ = true; const formData = Object.entries(invocation.args.data).reduce((acc, [key, value]) => { if (key === 'star' || key === 'type') { acc[key] = Number(value[0]); } else { acc[key] = value[0]; } return acc; }, {}); try { const data = await fetch('/api/comment', { method: "post", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData) }).then(res => res.json()); if (data.state === 0) { this.triggerEvent_('submitSuccess', { panelId: 'with_photo', message: '' }); return; } throw new Error(data.msg); } catch(e) { e = await e; this.triggerEvent_('submitError', {data: e.message}); } finally { this.loading_ = false; } }); this.registerAction('renderFormStar', async(invocation) => { this.triggerEvent_('rerenderFormStar', { star_color: this.starColor_ }); }) } mountCallback() { } getRevueConfigData_ = () => { fetch('/api/comment-config') .then(res => res.json()) .then(data => { this.config_ = data.data; // anonymous_permission 是否支持匿名 if (!this.config_.anonymous_permission) { const anonymousInput = this.form_.querySelector(`#${this.prefix}-revue-anonymous-${this.sectionId}`); anonymousInput.value = 'false'; anonymousInput.parentNode.classList.add('hidden', 'anonymous-permission-hidden'); } this.starColor_ = this.config_.star_color; if(this.accent_color && this.accent_color != 'null'){ this.starColor_ = this.accent_color; } // render star // star_color 星星颜色 const starEl = this.form_.querySelector(`#${this.prefix}-revue_write_modal_star-${this.sectionId}`); if (starEl) { SPZ.whenApiDefined(starEl).then((api) => { api.render({ star_color: this.starColor_ }); }); } }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomNewRevue); })() (function() { const TAG = 'spz-custom-revue-product-info-script'; class SpzCustomRevueProductInfoScript extends SPZ.BaseElement { constructor(element) { super(element); /** @private {!Element} */ this.product_id = null; } async buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.product_id = this.getProductId_(); this.triggerEvent_('init', { product_id: this.product_id }); try { const data = await this.getProductInfo_(); if (data?.data?.product) { this.triggerEvent_('finish', data.data.product); } } catch (error) { console.error('Failed to fetch product info:', error); // Handle the error appropriately } } getProductId_ = () => { return window.SHOPLAZZA.meta.page.resource_id; } async getProductInfo_() { if (!this.product_id) { console.error('Product ID is undefined or null'); return null; } try { const response = await fetch(`/api/products/${this.product_id}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('Error fetching product info:', error); throw error; // Rethrow to be caught by the caller } } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.LOGIC; } } SPZ.defineElement(TAG, SpzCustomRevueProductInfoScript); })() const TAG = 'spz-custom-revue-star'; class SPZCustomRevueStar extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.starNum = this.element.getAttribute('starNum'); this.starTotal = this.element.getAttribute('starTotal'); this.showStarText = this.element.getAttribute('showStarText'); this.starColor = this.element.getAttribute('color'); this.interact = this.element.getAttribute('interact'); this.starSize = this.element.getAttribute('starSize') || 14; } mountCallback = () => { this.doRender_({ starTotal: this.starTotal, totalArray: Array.from({ length: Number(this.starTotal) }, (v, k) => k + 1), starNum: this.starNum, showStarText: this.showStarText, starColor: this.starColor, starSize: this.starSize }).then(() => { if (this.interact) { this.addEventListeners_(); } }); } addEventListeners_ = () => { const stars = document.querySelectorAll('.revue-star__star'); stars.forEach(star => { star.addEventListener('click', event => { const starEl = star.closest('.revue-star__star'); const starIndex = Number(starEl.dataset.index); let isHalf = event.offsetX < star.offsetWidth / 2; // rtl if (document.documentElement.getAttribute('dir') === 'rtl') { isHalf = event.offsetX > star.offsetWidth / 2; } const starValue = isHalf ? starIndex - 0.5 : starIndex; this.starClickHandler_({ value: starValue }); }); }); } renderStar = () => { const isRtl = document.documentElement.getAttribute('dir') === 'rtl'; const stars = this.element.querySelectorAll('.revue-star__star'); stars.forEach((star, i) => { const starIndex = i + 1; const starEl = star.querySelector('svg:nth-child(2)'); const isHalf = this.starNum % 1 > 0 && Math.ceil(this.starNum) === starIndex; const isSolid = starIndex <= Math.ceil(this.starNum); starEl.style.display = isSolid ? 'block' : 'none'; if (isHalf) { if (isRtl) { // RTL布局下,如果是半星,显示星星的右半边 starEl.style.clipPath = `polygon(50% 0, 100% 0, 100% 100%, 50% 100%)`; } else { // LTR布局下,如果是半星,显示星星的左半边 starEl.style.clipPath = `polygon(0 0, 50% 0, 50% 100%, 0 100%)`; } } else { starEl.style.clipPath = `polygon(0 0, 100% 0, 100% 100%, 0 100%)` } }); const showCountEle = this.element.querySelector('#revue-star-show-count'); showCountEle && SPZ.whenApiDefined(showCountEle).then((api) => { api.render({ starNum: this.starNum, starTotal: this.starTotal }); }); } doRender_ = (data) => { return this.templates_ .findAndRenderTemplate(this.element, { starSize: this.starSize, ...data }, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }) .then(() => { this.starNum = data.starNum; this.renderStar(); }); } starClickHandler_ = (event) => { this.starNum = event.value; this.renderStar(); this.triggerEvent_('change', { value: event.value }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SPZCustomRevueStar) (function() { const TAG = 'spz-custom-new-revue-files-show'; class SpzCustomNewRevueFilesShow extends SPZ.BaseElement { constructor(element) { super(element); /** @private {!Element} */ this.files_ = [] } buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.setupAction_(); this.element.setAttribute('nums', this.files_.length); } mountCallback() { } setupAction_() { this.registerAction('upload', async(invocation) => { const uploadFileList = invocation.args?.data || []; uploadFileList.forEach(file => { if(this.files_.some(item => item.url === file.url)) return this.files_.push(file); }) this.doRender_(); }); this.registerAction('delete', async(invocation) => { this.files_ = this.files_.filter((_, index) => index !== invocation.args.index); this.doRender_(); this.triggerEvent_('delete', { count: this.files_.length, files: this.files_ }); }); this.registerAction('preview', async(invocation) => { let previewFileData = this.files_[invocation.args.index]; if (previewFileData.type === 'video') { previewFileData = {...this.parseVideoSrc_(previewFileData.url), ...previewFileData}; } this.triggerEvent_('preview', previewFileData); }); this.registerAction('clear', async(invocation) => { this.files_ = []; this.doRender_(); }); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } parseVideoSrc_(src) { const url = new URL(src); const params = new URLSearchParams(url.search); return { videoUrl: url.origin + url.pathname, mediaType: params.get('media_type'), vID: params.get('vID'), mp4: params.get('mp4'), hls: params.get('hls') }; } doRender_ = () => { this.triggerEvent_('setInputValue', { data: this.files_ .map(file => { const url = file.type === 'video' ? file.poster : file.url; return `${url}?width=${file.width}&height=${file.height}`; }) .join(',') }); this.element.setAttribute('nums', this.files_.length); return this.templates_ .findAndRenderTemplate(this.element, { files: this.files_ }) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }) } isLayoutSupported = (layout) => { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomNewRevueFilesShow); })() const TAG = 'spz-custom-revue-header'; class SPZCustomRevueHeader extends SPZ.BaseElement { constructor(element) { super(element); this.showCount = this.element.getAttribute('show-count'); } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback() { this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.showCount = this.element.getAttribute('show-count'); this.showSummary = this.element.getAttribute('show-summary'); this.showWriteReview = this.element.getAttribute('show-write-review'); this.showType = this.element.getAttribute('show-type') ; this.showSort = this.element.getAttribute('show-sort') ; this.sectionId = this.element.getAttribute('section-id'); this.viewall = this.element.getAttribute('viewall') ?? false; this.prefix = this.element.getAttribute('prefix'); } mountCallback() { } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } render(data) { const ndata = { ...data, showCount: this.showCount, showSummary: this.showSummary, showWriteReview: this.showWriteReview, showType: this.showType, showSort: this.showSort, } if(this.viewall == 'review'){ ndata.viewall = false } return this.templates_ .findAndRenderTemplate(this.element, ndata, null, true) .then(({el}) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }).then(() => { if(data && Object.keys(data).length > 0) { this.updateRender(data); this.setupSummaryContainerEffects_(data); } }); } updateRender(data) { this.renderStarCounts_(data); this.renderTypeSelect(data); this.renderSortSelect(data); } renderStarCounts_(data) { const renderData = { ...data.starData, star_color: data.star_color, isPC: data.isPC, } const summaryEle = data.isPC ? this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header_pc`) : this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header`); if(summaryEle) { SPZ.whenApiDefined(summaryEle).then((api) => { api.render(renderData); }); } } renderTypeSelect(data) { const typeSelect = this.element.querySelector(`#${this.prefix}-revue-header-type-${this.sectionId}`); if(typeSelect) { SPZ.whenApiDefined(typeSelect).then((api) => { api.render(data); api.registerAction('headerType_', (invocation) => { this.triggerEvent_('headerType', invocation.args.data); }); }); } } renderSortSelect(data) { const suffix = data.suffix || this.sectionId; const sortSelect = this.element.querySelector(`#${this.prefix}-revue-header-sort-${suffix}`); if(sortSelect) { SPZ.whenApiDefined(sortSelect).then((api) => { api.registerAction('headerSort_', (invocation) => { this.triggerEvent_('headerSort', invocation.args.data); }); }); } } setupSummaryContainerEffects_(data) { if(data.isPC) { this.setupSummaryContainerHover_(); } else { this.setupSummaryContainerTap_(); } } setupSummaryContainerHover_() { const summaryContainer = this.element.querySelector(`#revue-header-summary-container-${this.sectionId}`); const summaryEle = this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header_pc`); if (!summaryContainer || !summaryEle) return; let isHovering = false; // 鼠标移入容器时显示summary SPZUtils.Event.listen(summaryContainer, 'mouseenter', () => { isHovering = true; summaryEle.removeAttribute('hidden'); const selectIcon = summaryContainer.querySelector(`#revue-header-summary-icon-${this.sectionId}`); if(selectIcon) { selectIcon.classList.add('up-icon'); } }); // 鼠标移入summary时也保持显示 SPZUtils.Event.listen(summaryEle, 'mouseenter', () => { isHovering = true; }); // 鼠标移出容器时,检查是否还在summary上 SPZUtils.Event.listen(summaryContainer, 'mouseleave', () => { isHovering = false; setTimeout(() => { if (!isHovering) { summaryEle.setAttribute('hidden', 'true'); const selectIcon = summaryContainer.querySelector(`#revue-header-summary-icon-${this.sectionId}`); if(selectIcon) { selectIcon.classList.remove('up-icon'); } } }, 50); }); // 鼠标移出summary时,检查是否还在容器上 SPZUtils.Event.listen(summaryEle, 'mouseleave', () => { isHovering = false; setTimeout(() => { if (!isHovering) { summaryEle.setAttribute('hidden', 'true'); const selectIcon = summaryEle.querySelector(`#revue-header-summary-icon-${this.sectionId}`); if(selectIcon) { selectIcon.classList.remove('up-icon'); } } }, 50); }); } setupSummaryContainerTap_() { const selectIcon = this.element.querySelector(`#revue-header-summary-icon-${this.sectionId}`); const summaryEle = this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header`); if(!summaryEle) return; let isTapped = false; // 是否显示summary SPZ.whenApiDefined(summaryEle).then((api) => { api.registerAction('display', () => { if(isTapped) { isTapped = false; summaryEle.removeAttribute('hidden'); selectIcon.classList.add('up-icon'); } else { isTapped = true; summaryEle.setAttribute('hidden', 'true'); selectIcon.classList.remove('up-icon'); } }); }); } } SPZ.defineElement(TAG, SPZCustomRevueHeader); const TAG = 'spz-custom-revue-list'; class SPZCustomRevueList extends SPZ.BaseElement { constructor(element) { super(element); } static deferredMount() { return false; } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback = () => { this.element_id = this.element.getAttribute('id'); this.section_id = this.element.getAttribute('section-id'); this.suffix = this.element.getAttribute('suffix'); this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.isPC = window.innerWidth > (window.breakpoint || 960); } mountCallback = () => { // this.render({}); this.setAction() } render = (data) => { const ndata = { ...data, pc_layout: window.reviewProductSettings[this.section_id].pc_layout, m_loading_type: window.reviewProductSettings[this.section_id].m_loading_type, container_id: this.element_id, suffix: this.suffix, isProductPage: this.isProductPage, } return this.templates_ .findAndRenderTemplate(this.element, ndata, null) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }).then(() => { this.triggerEvent_('finish', {}); this.setupIntersectionObserver(); }); } renderList = (data, redo = false) => { const listEle = document.querySelector(`#revue-list-${this.suffix}`); const viewMoreEle = document.querySelector(`#revue-list-view-more`); const loadingEle = document.querySelector(`#revue-list-scroll-loading`); const viewMoreModal = document.querySelector(`#revue-viewall-modal-comp`); const reachBottomEle = document.querySelector(`#revue-list-reach-bottom-${this.suffix}`); if(viewMoreModal) { SPZ.whenApiDefined(viewMoreModal).then((api) => { api.setMarkScrollTop() }) } if (listEle) { SPZ.whenApiDefined(listEle).then((api) => { api.listRender(data, redo); }); } if(viewMoreEle) { if(data.hasmore) { viewMoreEle.removeAttribute('hidden'); } else { viewMoreEle.setAttribute('hidden', true); } } if (loadingEle) { if(data.hasmore) { loadingEle.removeAttribute('hidden'); } else { loadingEle.setAttribute('hidden', true); } } if (reachBottomEle) { if(data.hasmore) { reachBottomEle.setAttribute('hidden', true); } else { reachBottomEle.removeAttribute('hidden'); } } } setupIntersectionObserver = () => { // 创建 Intersection Observer 实例 const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const viewallModal = document.querySelector(`#revue-viewall-modal-comp`); if (viewallModal) { SPZ.whenApiDefined(viewallModal).then((api) => { api.loadMore(); }); } } }); }, { threshold: 0.1 // 当目标元素 10% 进入视区时触发 }); const loadingElement = document.querySelector('.revue-list-scroll-loading'); if (loadingElement) { observer.observe(loadingElement); } } setAction = () => { this.registerAction('checkOverFlow', () => { // 检查普通评论 this.element.querySelectorAll('.revue_text_line_4').forEach(elem => { if (elem.scrollHeight > elem.clientHeight + 10) { elem.classList.add('overflow-text'); } else { elem.classList.remove('overflow-text'); } }); // 检查回复内容 this.element.querySelectorAll('.revue_reply').forEach(elem => { const contentElem = elem.querySelector('.revue_reply_content'); if (contentElem.scrollHeight > contentElem.clientHeight + 10) { elem.classList.add('overflow-text'); } else { elem.classList.remove('overflow-text'); } }); }); } } SPZ.defineElement(TAG, SPZCustomRevueList); const TAG = 'spz-custom-revue-viewall-modal'; class SPZCustomRevueViewallModal extends SPZ.BaseElement { constructor(element) { super(element); } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback = () => { this.section_id = this.element.getAttribute('section-id'); this.action_ = SPZServices.actionServiceForDoc(this.element); this.templates_ = SPZServices.templatesForDoc(this.element); this.firstRender = true; this.markScrollTop = 0; this.scrollTop = 0; } mountCallback = () => { this.doRender_(); this.setupAction_(); } doRender_() { this.templates_ .findAndRenderTemplate(this.element, {}) .then((el) => { const children = this.element.querySelector('*:not(template)'); children && SPZCore.Dom.removeElement(children); this.element.appendChild(el); }).then(() => { const viewallModalContentEle = document.querySelector(`#revue-viewall-modal-content-${this.section_id}`); viewallModalContentEle.addEventListener('scroll', () => { this.scrollTop = viewallModalContentEle.scrollTop; }); }) } setupAction_() { this.registerAction('renderTab', async (invocation) => { if(this.firstRender) { this.firstRender = false; const productEle = document.querySelector(`#revue-product-compo`); const summaryEle = document.querySelector(`#revue-summary-${this.section_id}_viewall`); if (productEle) { SPZ.whenApiDefined(productEle).then(async (api) => { api.renderStarCounts({}, `revue-summary-${this.section_id}_viewall`); api.renderCommentTab({ viewall: false, write_review: false, scroll_loading: true }, `revue-tab-${this.section_id}_viewall`); }); } } }); this.registerAction('scrollToLast', async (invocation) => { const viewallModalContentEle = document.querySelector(`#revue-viewall-modal-content-${this.section_id}`); if(viewallModalContentEle) { requestAnimationFrame(() => { viewallModalContentEle.scrollTop = this.markScrollTop; }); } }); } setMarkScrollTop() { this.markScrollTop = this.scrollTop; } refresh() { this.firstRender = true; this.scrollTop = 0; const productEle = document.querySelector(`#revue-viewall-modal-${this.section_id}`); if (productEle) { SPZ.whenApiDefined(productEle).then(async (api) => { api.close(); }); } } loadMore() { const productEle = document.querySelector(`#revue-product-compo`); if (productEle) { SPZ.whenApiDefined(productEle).then(async (api) => { await api.renderByScrollPagination(`revue-comment-list-${this.section_id}_tab`, 'tab'); }); } } } SPZ.defineElement(TAG, SPZCustomRevueViewallModal); let section_id = '1731390051215'; window.reviewProductSettings = {}; const default_settings = { "star_least": "5", "only_featured": false, "only_media": false, "review_insufficient": "no_reviews", "mini_quantity": 5, "actions": "hide", "pc_layout": "single_column", "m_loading_type": "modal", "m_modal_page_limit": "3", "page_limit": 10, "display_product_link": false, "hide_review_section": true, "title": "Reviews", "title_color": "rgba(51, 51, 51, 1)", "primary_color": "rgba(48, 53, 77, 1)", "section_bg_color": "rgba(255, 255, 255, 1)", "background_color_new": "rgba(255, 255, 255, 1)" }; const user_settings = { "description_text": "Here are what our customers say.", "star_least": "5", "only_featured": false, "only_media": false, "review_insufficient": null, "mini_quantity": 5, "actions": null, "pc_layout": "single_column", "m_loading_type": null, "m_modal_page_limit": null, "comment_page_limit": 10, "page_limit": 10, "display_product_link": false, "hide_review_section": true, "title": "Customer Reviews", "accent_color": null, "title_color": "rgba(255, 255, 255, 1)", "text_color": "rgba(255, 255, 255, 1)", "section_bg_color": null, "background_color_new": null }; window.reviewProductSettings[section_id] = Object.assign({}, default_settings, user_settings, { page_limit: user_settings.comment_page_limit || user_settings.page_limit || default_settings.page_limit });