Factory Directly Wholesale, Worldwide Shipping. $3 discount for new customers, enter code "new" at checkout.
Home
Bundles
3 Bundles deal
4 Bundles deal
3 Bundles & closure
#613 bundles deal
Specials
Wig Style
Bob Wigs
Bang Wigs
Layered Cut Wigs
Short Curly Wigs
Braided Wigs
Short Wavy Wigs
Long Curly Wigs
Long Wavy Wigs
Straight Wigs
Wig Length
Pixie Wigs(under 6'')
Short Wigs(8''-14'')
Medium Length Wigs(16''-18'')
Long Wigs(20''-24'')
Extra Long Wigs(26''+)
Lace Design
5x5 Closure Wigs
7x6 Lace PartingMax Glueless Wigs
13x4 Full Lace Frontal Wigs
360 Lace Wigs
Full Lace Wigs
Minimalist Lace
Headband Wigs
13x6 Full Lace Frontal Wigs
U Part Wig
T Part Wig
No Lace Wigs
4x4 Lace Closure Wigs
Texture
Straight
Body Wave Wigs
Loose Wave Wigs
Deep Wave Wigs
Water Wave Wigs
Curly Wigs
Kinky Curly Wigs
Kinky Straight Wigs
Yaki Straight Wigs
Afro Wigs
Hair Color
Natural Black
Jet black
Brown
Blonde
Ombre
Highlight
Burgundy
Salt & Pepper
Blonde 613
All Colored Wigs
Red
Extensions
Hair wefts
Clip in hair
Tape in hair
Ponytail
Halo
Login
Register
Login
Register
Wigs
Quick View
5x5 Lace Closure Wig Curly Wig 20inches Brown Hair Pre Plucked Human Hair Wig
$150.00
Quick View
Pixie Wigs 100% Human Hair Short Cut Curly Wig no Lace Wigs
$35.00
Quick View
13x6 Full Lace Frontal Wig Deep Wave Wig 34inches Natural Black Hair Pre Plucked Human Hair Wig
$370.00
Quick View
5x5 Lace Closure Wig Curly Wig 20inches Black Hair Pre Plucked Human Hair Wig
$145.00
Quick View
13x4 Full Lace Frontal Wig WavyWig 26inches highlight Hair Pre Plucked Human Hair Wig
$210.00
Quick View
13x4 Full Lace Frontal Wig Wavy Wig 24inches black Hair Pre Plucked Human Hair Wig
$220.00
Quick View
5x5 Lace Closure Wig Wig Straight Wig 12inches Bob black Hair Pre Plucked Human Hair Wig
$110.00
Quick View
Curly Pixie Wigs 100% Human Hair Short Cut Curly Wig 13x4 Lace Front Wig
$75.00
Quick View
Bob Wigs 100% Human Hair Straight Wig 13x4 Full Lace Frontal Wig 12inches black Hair Pre Plucked
$100.00
Quick View
Bob Wigs 100% Human Hair Straight Wig 5x5 Lace Closure Wig 10inches black Hair Pre Plucked
$90.00
Quick View
Bob Wigs 100% Human Hair Straight Wig 5x5 Lace Closure Wig 10inches black Hair Pre Plucked
$80.00
Quick View
Bob Wigs 100% Human Hair Straight Wig 13x4 Full Lace Frontal Wig 16inches Burgundy Hair Pre Plucked
$130.00
Quick View
Bob Wigs 100% Human Hair Straight Wig 13x4 Full Lace Frontal Wig 16inches Black Hair Pre Plucked
$120.00
Quick View
Bob Wigs 100% Human Hair Straight Wig 13x4 Full Lace Frontal Wig 16inches Brown Hair Pre Plucked
$125.00
Quick View
Bob Wigs 100% Human Hair Straight Wig 5x5 Lace Closure Wig 14inches Black Hair Pre Plucked
$100.00
Quick View
5x5 Lace Closure Wig Kinky Straight Wig 24inches Black Hair Pre Plucked Human Hair Wig
$190.00
Quick View
5x5 Lace Closure Wig Wavy Wig 24inches Highlight Hair Pre Plucked Human Hair Wig
$195.00
Quick View
5x5 Lace Closure Wig Wavy Wig 22inches Auburn Hair Pre Plucked Human Hair Wig
$170.00
Quick View
5x5 lace closure wig yaki black color Pre-plucked transparent lace 8inch 250%density Premium raw hair
$89.00
Quick View
Bob Wigs 100% Human Hair Straight Wig 5x5 Lace Closure Wig 12inches Black Hair Pre Plucked
$100.00
Quick View
Pixie Wigs 100% Human Hair Short Cut Yaki+Pixie Cut Wig 5x5 Lace Closure Wig Salt and Pepper Color
$99.00
Quick View
13x4 Full Lace Frontal Wig Wavy Wig 24inches highlight Hair Pre Plucked Human Hair Wig
$199.00
Quick View
13x4 Full Lace Frontal Wig Wavy Wig 30inches black Hair Pre Plucked Human Hair Wig
$250.00
Quick View
5x5 Lace Closure Wig Wavy Wig 24inches Black Hair Pre Plucked Human Hair Wig
$180.00
Quick View
Glueless 360 Lace Wig Straight Black 24inch 250% Density Pre-Plucked Transparent Lace
$190.00
Quick View
5x5 Lace Closure Wig Wavy Wig 24inches Highlight Hair Pre Plucked Human Hair Wig
$195.00
Quick View
Pixie Wigs 100% Human Hair Short Cut Burgundy Pixie Cut Wig None Lace Wig
$75.00
Quick View
Glueless 360 Lace Wig Wavy Black 24inch 200% Density Pre-Plucked Transparent Lace
$185.00
Quick View
5x5 Lace Closure Wig Wavy Wig 24inches Highlight Hair Pre Plucked Human Hair Wig
$195.00
Quick View
5x5 Lace Closure Wig Wavy Wig 24inches Highlight Hair Pre Plucked Human Hair Wig
$195.00
View all
Join our list and save up to 20%
After you join our email list,we‘ll send you a special discount code that will apply to your first order.
Thanks for Subscribing!
We will send news to your email.
let section_id = '1666752808616'; window.reviewSettings = {}; window.reviewSettings[section_id] = { "sub_title": "Subscribe to get special offers, free giveaways, and once-in-a-lifetime deals.", "star_least": "4", "only_featured": false, "with_photo": true, "review_insufficient": null, "minimum_comment_num": 5, "fill_strategy": null, "layout": "grid", "image_size": "natural", "wall_mobile_num": 2, "wall_pc_num": 4, "limit": 8, "show_product": true, "hide_review_section": true, "title": "Let Customers Speak for us", "accent_color": null, "color_title": "#000000", "text_color": "#000000", "card_wrap_color": null, "background_color": "#ffffff" };
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)
${function(){ return `
${data.starNum}
/
${data.starTotal}
`; }()}
${function(){ return `
${data.showStarText === 'true' ? `
${data.starNum}
/
${data.starTotal}
` : ''}
`; }()}
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() { return `
${data.count > 99 ? '99+' : data.count < 1 ? '' : data.count}
`; }()}
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)
${function() { return `
${function() { if(data.imgCover) { if(media.videosrc) { let src = ''; if (media.videosrc) { src = media.videosrc + '.' + media.ext; } const videoDom = `
`; if(!isPC){ return `
${videoDom}
` } return `
${videoDom}
` } else if(media.mp4 || media.hls) { const videoDom = `
`; if(!isPC){ return `
${videoDom}
` } return `
${videoDom}
` } else { if(!isPC){ return `
` }else{ return `
` } } } else { if (media.videosrc) { let src = ''; if (media.videosrc) { src = media.videosrc + '.' + media.ext; } return `
` } else if(media.mp4 || media.hls) { return `
` } else { return `
` } } }()}
`; }()}
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)
${function() { return `
Newest
` }()}
${function() { return `
Newest
Most liked
Highest ratings
Lowest ratings
` }()}
${function() { return `
Newest
Most liked
Highest ratings
Lowest ratings
` }()}
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') || '1666752808616'; 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-flow'; class SpzCustomRevueFlow extends SPZ.BaseElement { constructor(element) { super(element); this.sectionId = this.element.getAttribute('section-id'); this.show_product = ''; this.with_photo = ''; this.limit = ''; this.star_least = ''; this.layout = '' this.wall_pc_num = '' this.wall_mobile_num = '' this.accent_color = '' this.isProductPage = '15' == 1; this.isCollectionPage = '15' == 2; this.isCartPage = '15' == 13; this.lastWidth = window.innerWidth; } static deferredMount() { return false; } buildCallback = () => { this.action_ = SPZServices.actionServiceForDoc(this.element); this.xhr_ = SPZServices.xhrFor(this.win); this.setupAction_(); const url = new URL(window.location.href); const preview_theme_id = url.searchParams.get('preview_theme_id'); if (preview_theme_id) { this.preview_theme_id = preview_theme_id; } this.commentConfig = {}; this.sort = 'created_at'; this.direction = 'desc'; this.isPC = window.innerWidth > (window.breakpoint || 960); this.appendList = []; this.commentListRes = []; this.cardConfig = window.reviewSettings[this.sectionId]; } render_ = (data={}) => { const {star_least, with_photo, show_product, limit, layout, wall_pc_num, wall_mobile_num, accent_color, fill_strategy, review_insufficient, minimum_comment_num, only_featured, hide_review_section} = this.cardConfig; Object.assign(this, {star_least, with_photo, show_product, limit, layout, wall_pc_num, wall_mobile_num, accent_color, fill_strategy, review_insufficient, minimum_comment_num, only_featured, hide_review_section}); if(this.layout === 'wall'){ this.with_photo = 1; }; this.params = { offset: this.appendList.length || 0, sort_by: this.sort, sort_direction: this.direction, show_reply: 1 || this.commentConfig.show_reply ? 1 : 0, with_photo: this.with_photo, ...data } if(this.fill_strategy == 'store'){ if(this.review_insufficient == 'less_than'){ this.params.fill_min_threshold = minimum_comment_num; }else{ this.params.fill_min_threshold = 1; } this.params.fill_strategy = this.fill_strategy; } const summaryObj = { star_least:this.star_least, product_ids: this.isProductPage ? '' : this.isCartPage ? '' : '', collection_id: this.isCollectionPage ? '' : '', filter_type: (this.isProductPage || this.isCartPage) ? 'product' : this.isCollectionPage ? 'collection' : 'store', fill_strategy: this.params?.fill_strategy || '', only_media: !!this.params.with_photo, only_featured:this.only_featured } if(this.params.fill_min_threshold){ summaryObj.fill_min_threshold = this.params.fill_min_threshold; } Promise.all([ this.fetchSummary_(summaryObj), this.fetchCommentConfig_(), this.fetchCommentList_(this.params) ]).then(response => { const [starCountRes,commentConfigRes, commentListRes] = response; this.commentConfig = commentConfigRes.data; this.commentConfig.show_product = this.show_product; this.commentListRes = commentListRes; this.starCountRes = starCountRes; const showEmpty = this.review_insufficient == 'less_than' && commentListRes?.data?.count < this.minimum_comment_num && this.fill_strategy == 'hide' const showEmpty2 = (!commentListRes?.data?.count || commentListRes?.data?.count*1 == 0) && (this.hide_review_section || this.fill_strategy == 'hide') if(showEmpty2 || showEmpty){ this.renderEmpty_(); return; } if (this.preview_theme_id) { this.fetchThemeConfig_(this.preview_theme_id).then(themeConfig => { if (themeConfig?.star_color) { this.commentConfig.star_color = themeConfig.star_color; } if(this.accent_color && this.accent_color != 'null'){ this.commentConfig.star_color = this.accent_color; } }); } const colums = this.calculateColums_(); this.renderFlowMain_({ config: this.commentConfig, comment: commentListRes.data, column_count: colums }).then(() => { this.renderHeader_({ starData: this.starCountRes.data, listData: this.commentListRes.data, star_color: this.commentConfig.star_color, comment_avg_star: this.commentListRes.data.avg_star, comment_count: this.commentListRes.data?.count, isPC: this.isPC, }); this.renderStarCounts_(this.starCountRes.data); this.addImpression(`[data-section-id="${this.sectionId}"] .revue_container`); this.renderCommentList_({ list: commentListRes.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name },true); }); window.removeEventListener('resize', this.rerenderFn); window.addEventListener('resize', this.rerenderFn); }) .catch(error => { this.renderEmpty_(); console.error('error', error); }); } mountCallback = () => { this.render_() } fetchCommentConfig_ = async () => { const response = await fetch('/api/comment-config'); return response.json(); } fetchSummary_ = async (data) => { const response = await fetch('/api/v1/comments/summary',{ method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response.json(); } fetchCommentList_ = async(data) => { const response = await fetch(`/api/v1/comments`,{ method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ ...data, offset: data.offset, show_product:!!this.show_product, star_least:this.star_least, limit:this.limit, sort_by:data.sort_by || 'created_at', sort_direction: data.sort_direction || 'desc', filter_type:(this.isProductPage || this.isCartPage) ? 'product' : this.isCollectionPage ? 'collection' : 'store', show_reply: !!data.show_reply, only_media: !!data.with_photo, product_ids: this.isProductPage ? '' : this.isCartPage ? '' : '', collection_id: this.isCollectionPage ? '' : '', only_featured: this.only_featured, }) }); if(response.status != 200){ return Promise.reject(false); } return response.json(); } fetchThemeConfig_ = async(themeId) => { const response = await fetch(`/api/comment/theme-config?theme_id=${themeId}`); return response.json(); } renderEmpty_ = () => { const holderEl = document.getElementById(`revue_no_data_placeholder_${this.sectionId}`); const skeleton = document.getElementById(`revue_flow_skeleton-${this.sectionId}`); if(skeleton){ skeleton.style.display = 'none'; }; if (window.top !== window.self) { SPZ.whenApiDefined(holderEl).then((api) => { api.render({}, true); }); }else{ holderEl.style.display = 'none'; } } renderFlowMain_ = async (data) => { const mainEle = document.querySelector(`#revue_flow_render-${this.sectionId}`); if (mainEle) { const api = await SPZ.whenApiDefined(mainEle); return api.render({ ...data },true); } } calculateColums_ = () => { let colums = 1; this.isPC = window.innerWidth > (window.breakpoint || 960); if (this.layout === 'grid') { colums = this.isPC ? 4 : 2; } else { colums = this.isPC ? 2 : 1; } if(this.layout == 'wall'){ colums = this.isPC ? (this.wall_pc_num || 4) : (this.wall_mobile_num || 2); } return colums } rerenderFn = (list) => { try{ if(!this?.commentListRes?.data) return; const currentWidth = window.innerWidth; if (currentWidth == this.lastWidth ) { return } else { this.lastWidth = currentWidth; } const throttleHandle = SPZCore.Types.throttle(window,()=>{ let colums = this.calculateColums_(); this.renderFlowMain_({ config: this.commentConfig, comment: this.commentListRes.data, column_count: colums }).then(() => { this.renderHeader_({ starData: this.starCountRes.data, listData: this.commentListRes.data, star_color: this.commentConfig.star_color, comment_avg_star: this.commentListRes.data.avg_star, comment_count: this.commentListRes.data?.count, isPC: this.isPC, }); this.renderStarCounts_(this.starCountRes.data); this.renderCommentList_({ list: this.commentListRes.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name }, true); }); },200) throttleHandle() }catch(e){ console.log(e); } } renderCommentList_ = (data,redo=false) => { if(this.accent_color && this.accent_color != 'null'){ this.commentConfig.star_color = this.accent_color; } const listEle = document.querySelector(`#revue_flow_list-${this.sectionId}`); if (listEle) { const current_list = data.list.list.map((item, index) => { return { ...item, config: this.commentConfig, index: data.sorted ? index : this.appendList.length + index, shop_name: window.SHOPLAZZA.shop.shop_name } }); if (data.sorted) { this.appendList = current_list; SPZ.whenApiDefined(listEle).then((api) => { api.listRender({ count: data.list.count, list: current_list },true); }); } else { let obj = {}; this.appendList = this.appendList.concat(current_list).reduce((cur,next) => { obj[next.id] ? "" : obj[next.id] = true && cur.push(next); return cur; },[]); SPZ.whenApiDefined(listEle).then((api) => { api.listRender({ count: data.list.count, list: current_list },redo); }); } }; this.renderLoadMoreBtn(data.list); } mediaParse_ = function (url) { var result = {}; try { url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) { try { result[key] = decodeURIComponent(value); } catch (e) { result[key] = value; } }); result.src = url.split('?')[0]; } catch (e) {}; return result; } impFunc = function (selector, cb) { // 添加自动曝光 const el = document.querySelector(selector); const onImpress = () => { cb(); }; // 元素未曝光时添加曝光事件监听,已曝光则可以立刻触发处理器 if (el && !el.getAttribute('imprsd')) { el.addEventListener('impress', onImpress); } else if (el) { onImpress(); } }; addImpression = function (selector) { this.impFunc(selector, () => { window.sa && window.sa.track('plugin_reviews_masonry_pv', { layout_type: this.layout, level_type: this.star_least, show_number: this.limit, plugin_timestamp: new Date().valueOf().toString(), reviews_num: this.appendList.length }); }); }; addModalImpression = function (selector, params) { this.impFunc(selector, () => { window.sa && window.sa.track('plugin_reviews_modal_pv', { ...params, plugin_timestamp: new Date().valueOf().toString(), }); }); }; setupAction_ = () => { this.registerAction('refresh', async(invocation) => { this.render_({ ...this.params, offset: 0, sort_by: 'created_at', sort_direction: 'desc', show_reply: true, with_photo: false, }) }); this.registerAction('renderTypeChangeList', async(invocation) => { const {type,direction } = invocation.args.data; this.with_photo = type === 'with_photo'; this.direction = direction; this.params = { ...this.params, offset: 0, sort_by: this.sort, sort_direction: this.direction, show_reply: 1, with_photo: this.with_photo }; this.fetchCommentList_(this.params).then(response => { this.renderCommentList_({ sorted: true, list: response.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name }, true); }); }) this.registerAction('renderSortedList', async(invocation) => { const {sort, direction} = invocation.args.data; this.sort = sort; this.direction = direction; const panelId = this.panelId; this.params = { ...this.params, offset: 0, sort_by: this.sort, sort_direction: this.direction, show_reply: 1 , with_photo: this.with_photo } this.fetchCommentList_(this.params).then(response => { this.renderCommentList_({ sorted: true, list: response.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name }, true); }); }); this.registerAction('renderProductCommentModal', async(invocation) => { const id = invocation.args.id; const current = this.appendList?.find(_data => _data.id == id); const modalEle = document.querySelector(`#revueDetailModal-${this.sectionId}`); const imgArr = current.img.map(image => { const width = this.getUrlKey('width', image); const height = this.getUrlKey('height', image); return { width, height, rate: (height/width).toFixed(2)*100, url: image }; }); if (modalEle) { SPZ.whenApiDefined(modalEle).then((api) => { api.renderModalFn({ data: { ...current, img: imgArr }, commentConfig: this.commentConfig, layout: this.layout, level_type: this.star_least, show_number: this.limit }); }); } }); this.registerAction('loadMore', async(invocation) => { this.params = { ...this.params, offset: this.appendList.length, sort_by: this.sort, sort_direction: this.direction, show_reply: 1 || this.commentConfig.show_reply ? 1 : 0, with_photo: this.with_photo } this.fetchCommentList_(this.params).then(response => { this.renderCommentList_({ list: response.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name }); }); }); } getUrlKey = (name, url) => { return ( decodeURIComponent( (new RegExp("[?|&]" + name + "=" + "([^&;]+?)(&|#|;|$)").exec( url ) || [, ""])[1].replace(/\+/g, "%20") ) || null ); } renderHeader_ = (data) => { if(this.accent_color && this.accent_color != 'null'){ data.star_color = this.commentConfig.star_color = this.accent_color; } const headerEle = document.querySelector(`#review-revue-header-${this.sectionId}`); if (headerEle) { SPZ.whenApiDefined(headerEle).then(async (api) => { api.render(data); }); } } renderStarCounts_ = (data) => { const summaryEle = document.querySelector(`#revue-summary-${this.sectionId}`); if (summaryEle) { SPZ.whenApiDefined(summaryEle).then((api) => { api.render({ ...data, star_color: this.commentConfig.star_color }); }); } } renderLoadMoreBtn = (data) => { const loadEle = document.querySelector(`#revue_flow_load_more_render-${this.sectionId}`); if (loadEle) { SPZ.whenApiDefined(loadEle).then((api) => { api.render({ comment: data }, true); }); } } triggerEvent_(name, data) { const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {}); this.action_.trigger(this.element, name, event); } unmountCallback(){ } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement(TAG, SpzCustomRevueFlow)
${function() { const randomStr = Math.random().toString(36).substring(7); const formatDate = value => { let date = new Date(value * 1000); const day = date.toLocaleString('en-US', { day: '2-digit' }); const month = date.toLocaleString('en-US', { month: 'short' }); const year = date.toLocaleString('en-US', { year: 'numeric' }); return month + ' ' + day + ', ' + year; }; const item = data; const config = data.config; const mimic_mobile_style = data?.mimic_mobile_style; return `
${data.img.map(media => { return `
`; }).join('')}
${data.img.map((media, i) => { return `
` }).join('')}
${data.img.map((media, i) => { return `
` }).join('')}
${item.username}
.${item.iso_code_3}
${formatDate(item.created_at)}
Verified
${item.content}
${data.shop_name} reply:
${item.reply && item.reply.length && item.reply[0].content}
${item.product?.title}
`; }()}
const TAG = 'spz-custom-revue-modal'; class SPZCustomRevueModal extends SPZ.BaseElement { constructor(element) { super(element); this.renderedId = ''; this.closeCB = null; this.sectionId = this.element.getAttribute('section-id'); } static deferredMount() { return false; } buildCallback = () => { this.setupAction_(); } mountCallback = () => { } setupAction_ = () => { this.registerAction('renderModal', this.renderModalFn) this.registerAction('closeFn',() => { this?.closeCB?.() }) } mediaParse_ = function (url) { var result = {}; try { url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) { try { result[key] = decodeURIComponent(value); } catch (e) { result[key] = value; } }); result.src = url.split('?')[0]; } catch (e) {}; return result; } impFunc = function (selector, cb) { // 添加自动曝光 const el = document.querySelector(selector); const onImpress = () => { cb(); }; // 元素未曝光时添加曝光事件监听,已曝光则可以立刻触发处理器 if (el && !el.getAttribute('imprsd')) { el.addEventListener('impress', onImpress); } else if (el) { onImpress(); } }; addModalImpression = function (selector, params) { this.impFunc(selector, () => { window.sa && window.sa.track('plugin_reviews_modal_pv', { ...params, plugin_timestamp: new Date().valueOf().toString(), }); }); }; renderModalFn(receivedData){ if(!receivedData) return; const { data:current, commentConfig, layout, level_type, show_number, closeCB, mimic_mobile_style, props } = receivedData; try{ if(closeCB){ this.closeCB = () => { closeCB() }; } }catch(e){ console.log(e); }; const commentModalEl = document.querySelector(`#revue-product-comment-modal-${this.sectionId}`); const modalRenderEl = document.querySelector(`#revue_flow_modal_render-${this.sectionId}`); if (!!mimic_mobile_style) { if (commentModalEl) { commentModalEl.classList.add('mobile-wrap'); } if (modalRenderEl) { modalRenderEl.classList.add('w-h-full-h5'); } }; const parsedImages = current?.img?.map(image => { return this.mediaParse_(`${image.url}?width=${image.width}&height=${image.height}`); }); const modalEle = document.querySelector(`#revue_flow_modal_render-${this.sectionId}`); if (modalEle) { SPZ.whenApiDefined(modalEle).then((api) => { api.render({ ...current, img: parsedImages, config: commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name, mimic_mobile_style }, true).then(() => { this.addModalImpression('.revue_modal_container', { id: current.id, username: current.username, content: current.content, star: current.star, is_verified: current.is_verified, is_featured: current.is_featured, anonymous: current.anonymous, iso_code_3: current.iso_code_3, like_count: current.like, layout_type: layout, level_type: level_type, show_number: show_number, }); }).then(()=>{ this.renderedId = current.id }); }); } } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } } SPZ.defineElement('spz-custom-revue-modal', SPZCustomRevueModal)
${function() { return `
${function(){ if (media.videosrc) { const src = media.videosrc + '.' + media.ext; return `
` } else if(media.mp4 || media.hls) { return `
` } else { return `` } }()}
`; }()}
const TAG = 'spz-custom-revue-video'; class SPZCustomRevueVideo 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); // 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; } loadVideo = () => { this.doRender_({ images: this.images, isPC: this.isPC }).then(()=>{ this.triggerEvent_('connected', {}); }) } mountCallback = () => { this.loadVideo(); this.registerAction('loadVideo', async(invocation) => { this.loadVideo(); }) } 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, SPZCustomRevueVideo)
${function(){ const isPC = data.isPC; const pc_layout = 'single_column'; const m_loading_type = 'modal'; const showCount = data.showCount ?? (!isPC || pc_layout !== 'single_column'); const showSummary = data.showSummary ?? (!isPC || pc_layout !== 'single_column'); const showWriteReview = data.showWriteReview ?? (isPC && pc_layout !== 'single_column'); const viewall = data.viewall ?? (!isPC && m_loading_type === 'modal'); const showType = data.showType ?? ((!isPC || pc_layout !== 'single_column') && !viewall); const showSort = data.showSort ?? ((!isPC || pc_layout !== 'single_column') && !viewall); if(!data.suffix){ data.suffix = '1666752808616' } return `
Let Customers Speak for us
(${data.comment_count > 500 ? '500+' : (data.comment_count || 0)})
Write a Review
View all
` }()}
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);
${function(){ const starOrder = ['one_star', 'two_star', 'three_star', 'four_star', 'five_star']; function sortStarRatings(ratings) { const sortedRatingsArr = []; starOrder.map((star,index) => { sortedRatingsArr.push(index+1); return star; }); return sortedRatingsArr; }; const star_levels = sortStarRatings(data.star_detail).reverse(); return `
${data.comment_avg_star}
Total reviews: ${data.comment_count > 999 ? '999+' : data.comment_count}
${level}
`; }()}
${function() { return `
All
` }()}
${function() { const list = data.listData; return `
All(${list.count})
With Photos(${list.image_count})
` }()}
${function() { const list = data.listData; return `
All(${list.count})
With Photos(${list.image_count})
` }()}
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') || '1666752808616'; 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)
${function() { const isPercentage = data.show_percentage === 'true' && data.total <= data.show_percentage_num; return `
${!isPercentage ? `${data.count}` : `${data.count / data.total * 100}%`}
` }()}
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)
${function() { const item = data.originData; const config = data.config; item.imgArr = item.img.map(image => { const width = getUrlKey('width', image); const height = getUrlKey('height', image); return { width, height, rate: (height/width).toFixed(2)*100, url: image }; }); const randomStr = Math.random().toString(36).substring(7); const formatDate = value => { let date = new Date(value * 1000); const day = date.toLocaleString('en-US', { day: '2-digit' }); const month = date.toLocaleString('en-US', { month: 'short' }); const year = date.toLocaleString('en-US', { year: 'numeric' }); return month + ' ' + day + ', ' + year; }; function getUrlKey(name, url) { return ( decodeURIComponent( (new RegExp("[?|&]" + name + "=" + "([^&;]+?)(&|#|;|$)").exec( url ) || [, ""])[1].replace(/\+/g, "%20") ) || null ); } let videoMp4 = ''; let videoHls = ''; if(item.imgArr.length > 0 && item.imgArr[0].url?.includes('media_type=video')){ videoMp4 = getUrlKey('mp4',item.imgArr[0].url); videoHls = getUrlKey('hls',item.imgArr[0].url); } function mediaParse_(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; } const parsedImages = item?.img.map(image => { return mediaParse_(image); }); return `
${item.username}
${item.iso_code_3}
${item.content}
${data.shop_name} reply:
${item.reply && item.reply.length && item.reply[0].content}
${formatDate(item.created_at)}
${item.product.title}
`; }()}
${function() { const item = data.originData; const config = data.config; item.imgArr = item.img.map(image => { const width = getUrlKey('width', image); const height = getUrlKey('height', image); return { width, height, rate: (height/width).toFixed(2)*100, url: image }; }); const randomStr = Math.random().toString(36).substring(7); const formatDate = value => { let date = new Date(value * 1000); const day = date.toLocaleString('en-US', { day: '2-digit' }); const month = date.toLocaleString('en-US', { month: 'short' }); const year = date.toLocaleString('en-US', { year: 'numeric' }); return month + ' ' + day + ', ' + year; }; function getUrlKey(name, url) { return ( decodeURIComponent( (new RegExp("[?|&]" + name + "=" + "([^&;]+?)(&|#|;|$)").exec( url ) || [, ""])[1].replace(/\+/g, "%20") ) || null ); } let videoMp4 = ''; let videoHls = ''; if(item.imgArr.length > 0 && item.imgArr[0].url?.includes('media_type=video')){ videoMp4 = getUrlKey('mp4',item.imgArr[0].url); videoHls = getUrlKey('hls',item.imgArr[0].url); } function mediaParse_(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; } const parsedImages = item?.img.map(image => { return mediaParse_(image); }); return `
+${item.imgArr.length}
${item.content}
${item.product.title}
${item.username}
${formatDate(item.created_at)}
`; }()}
${function() { const item = data.originData; const config = data.config; item.imgArr = item.img.map(image => { const width = getUrlKey('width', image); const height = getUrlKey('height', image); return { width, height, rate: (height/width).toFixed(2)*100, url: image }; }); const randomStr = Math.random().toString(36).substring(7); const formatDate = value => { let date = new Date(value * 1000); const day = date.toLocaleString('en-US', { day: '2-digit' }); const month = date.toLocaleString('en-US', { month: 'short' }); const year = date.toLocaleString('en-US', { year: 'numeric' }); return month + ' ' + day + ', ' + year; }; function getUrlKey(name, url) { return ( decodeURIComponent( (new RegExp("[?|&]" + name + "=" + "([^&;]+?)(&|#|;|$)").exec( url ) || [, ""])[1].replace(/\+/g, "%20") ) || null ); } let videoMp4 = ''; let videoHls = ''; if(item.imgArr.length > 0 && item.imgArr[0].url?.includes('media_type=video')){ videoMp4 = getUrlKey('mp4',item.imgArr[0].url); videoHls = getUrlKey('hls',item.imgArr[0].url); } function mediaParse_(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; } const parsedImages = item?.img.map(image => { return mediaParse_(image); }); return `
+${item.imgArr.length}
${item.username}
.${item.iso_code_3}
`; }()}
View more
Wow you reached the bottom
${function(){ return `
No reviews available. The product reviews component has been hidden
Review Flow
` }()}