🎁Get a free gift with any order. No Import Charges

💥2nd 20% Off, 3rd 50% Off,0% Installments

🚚Free Shipping $59+. 5 Business Days Delivery

🎁Get a free gift with any order. No Import Charges

💥2nd 20% Off, 3rd 50% Off,0% Installments

🚚Free Shipping $59+. 5 Business Days Delivery

  • Log in
  • Create an account
  • Best Sellers hot
  • Rose Toy hot
  • For Her
    • Dildos
    • Tongue Vibrators
    • Rabbit Vibrators
    • Nipple Vibrators
  • For Him
    • Male Masturbators
    • Cock Rings
    • Prostate Massagers
    • Pocket Pussy
    • Penis Pump
  • Vibrators
  • Male Masturbators
  • Dildos
  • Lube & Accessories
  • New Arrival
  • More links
  • Best Sellers hot
  • Rose Toy hot
  • For Her
    • Dildos
    • Tongue Vibrators
    • Rabbit Vibrators
    • Nipple Vibrators
  • For Him
    • Male Masturbators
    • Cock Rings
    • Prostate Massagers
    • Pocket Pussy
    • Penis Pump
  • Vibrators
  • Male Masturbators
  • Dildos
  • Lube & Accessories
  • New Arrival
  • More links

  • Log in
  • Create an account
  • Best Sellers hot
  • Rose Toy hot
  • For Her
    • For Her
    • Dildos
    • Tongue Vibrators
    • Rabbit Vibrators
    • Nipple Vibrators
  • For Him
    • For Him
    • Male Masturbators
    • Cock Rings
    • Prostate Massagers
    • Pocket Pussy
    • Penis Pump
  • Vibrators
  • Male Masturbators
  • Dildos
  • Lube & Accessories
  • New Arrival
  • Log in
  • Create an account
  • (function(){ let w = window.innerWidth; function setHeaderCssVar() { const headerEle = document.getElementById('shoplaza-section-header'); if(!headerEle){ return }; document.body.style.setProperty('--window-height', `${window.innerHeight}px`); document.body.style.setProperty('--header-height', `${headerEle.clientHeight}px`); const mdScorllHideEle = headerEle.querySelector('.header__mobile .header__scroll_hide'); if (mdScorllHideEle) { document.body.style.setProperty('--header-scroll-hide-height-md', `${mdScorllHideEle.clientHeight}px`); } const pcScorllHideEle = headerEle.querySelector('.header__desktop .header__scroll_hide'); if (pcScorllHideEle) { document.body.style.setProperty('--header-scroll-hide-height-pc', `${pcScorllHideEle.clientHeight}px`); } } function handlResize() { if(w == window.innerWidth){return}; w = window.innerWidth; setHeaderCssVar(); }; function init(){ setHeaderCssVar(); window.removeEventListener('resize', window._theme_header_listener) window._theme_header_listener = handlResize; window.addEventListener('resize', window._theme_header_listener); } init(); })();
    Home  /  Detailed Instructions for Use Rose Vibrators

    Detailed Instructions for Use Rose Vibrators

    How to Use The Original Rose Toy By Rose Toy How to Use The Original Rose Toy By Rose Toy
    How to Use The Original Rose Toy by Scarlett Mitchell
    How to Use The Rose Toy with Bullet by Scarlett Mitchell
    How to Use The Rose Toy with Tongue by Scarlett Mitchell
    2 in 1 High-Frequency Rose with Tongue by Scarlett Mitchell
    How to Use The Wand Massager with Rose Clit Sucker by Scarlett Mitchell
    How to Use The Love Flower Sexual Toy by Scarlett Mitchell
    How to Use The 2.0 Rose With Tongue Clit Licker by Scarlett Mitchell
    How to Use The 2in1 Sucking Rose Toy with Tongue by Scarlett Mitchell
    How to Use the 3in1 Tongue Licking Thrusting Vibrating Rose Adult Toy by Scarlett Mitchell

    Information

    • About Us
    • Contact Us
    • Rosetoy Rewards
    • DECLARATION
    • FAQ
    • How to Track Order
    • Track Your Order
    • Discreet Packaging
    • Payment Methods
    • Influencer Program
    • Affiliate Program
    • Sitemap

    Information

    • About Us
    • Contact Us
    • Rosetoy Rewards
    • DECLARATION
    • FAQ
    • How to Track Order
    • Track Your Order
    • Discreet Packaging
    • Payment Methods
    • Influencer Program
    • Affiliate Program
    • Sitemap

    Support

    • Shipping & Delivery
    • Cancellation Policy
    • Return Policy
    • Terms & Conditions
    • Privacy Policy
    • Intellectual Property Rights
    • Price Guarantee Policy
    • Damaged Replacement Policy

    Support

    • Shipping & Delivery
    • Cancellation Policy
    • Return Policy
    • Terms & Conditions
    • Privacy Policy
    • Intellectual Property Rights
    • Price Guarantee Policy
    • Damaged Replacement Policy

    Get in touch

    • service@rosetoy-official.com
    • WhatsApp: +16464883235

    Get in touch

    • service@rosetoy-official.com
    • WhatsApp: +16464883235

    Follow us

    Follow us

    We accept

    • PayPal
    • Visa
    • Mastercard
    • American Express
    • Apple Pay
    • Google Pay

    We accept

    • PayPal
    • Visa
    • Mastercard
    • American Express
    • Apple Pay
    • Google Pay
    © 2025 Rosetoy Official All rights reserved
    View Cart
    const debounce = function(fn, delay, immediate = false) { let timer; let promise; return function () { const context = this; const args = arguments; // 如果已经存在 promise,直接返回(等待正在执行的) if (promise) { return promise; } // 创建新的 promise promise = new Promise((resolve, reject) => { if (immediate) { // 立即执行 try { const result = fn.apply(context, args); // 如果结果是 promise,等待它完成 if (result && typeof result.then === 'function') { result.then(resolve).catch(reject); } else { resolve(result); } } catch (error) { reject(error); } // 设置定时器,在 delay 时间后重置 promise,允许下次调用 timer = setTimeout(() => { promise = null; }, delay); } else { // 延迟执行 timer = setTimeout(() => { try { const result = fn.apply(context, args); // 如果结果是 promise,等待它完成 if (result && typeof result.then === 'function') { result.then(resolve).catch(reject); } else { resolve(result); } } catch (error) { reject(error); } // 重置 promise promise = null; }, delay); } }); return promise; }; }; class HomeData { constructor() { this.earnPointsPlan = Promise.resolve({}); this.redeemPlan = Promise.resolve({}); this.getMemberDetailDebounce = debounce( this.getMemberDetail.bind(this), 200, true // 首次立即执行 ); this.memberDetail = this.getMemberDetailDebounce(); } refreshAllData() { this.getMemberDetailDebounce(); } getMemberDetail() { const memberPromise = fetch( "\/api\/loyalty-server\/member" ).then((response) => { // not login // 用null 和undefined来区分用户状态,因为已经很多地方用!!data.member来判断了,这是最简单的方式。 // null: not member // undefined: not login if (response.status === 401) { return undefined; } else if (response.status === 404) { // not member return null } else if (!response.ok) { return null } return response.json(); }); const tierDetail = fetch( "\/api\/loyalty-server\/tier-details" ).then((response) => response.json()); const fetchPromise = Promise.all([memberPromise, tierDetail]).then(([memberDetail, tierList]) => { const currentTierIndex = tierList.tier_details.findIndex((tier) => tier.id === memberDetail?.tier_id) return { member: memberDetail, current_tier: tierList.tier_details[currentTierIndex === -1 ? 0 : currentTierIndex], next_tier: currentTierIndex === tierList.tier_details.length - 1 ? null : tierList.tier_details[currentTierIndex + 1] } }) this.memberDetail = fetchPromise; return fetchPromise; } getPointPlans(eventType) { const url = "\/api\/loyalty-server\/earn-points\/campaigns" const fetchPromise = fetch(`${url}${eventType ? `?event_types=${eventType}` : ''}`).then((response) => response.json()); this.earnPointsPlan = fetchPromise; return fetchPromise; } getPointsDeduction() { const fetchPromise = fetch( "\/api\/loyalty-server\/points-deduction\/campaign" ).then((response) => response.json()); this.pointsDeduction = fetchPromise; return fetchPromise; } getCompleteTipStorage() { return Promise.resolve(window.sessionStorage.getItem('loyalty-complete-tip-status')); } saveCompleteTipStorage() { return Promise.resolve(window.sessionStorage.setItem('loyalty-complete-tip-status', 'true')); } } const initData = new HomeData(); exportFunction("memberDetail", () => initData.memberDetail); exportFunction("earnPointData", () => initData.earnPointsPlan); exportFunction("refreshAllData", initData.refreshAllData.bind(initData)); exportFunction("refreshMemberDetail", initData.getMemberDetail.bind(initData)); exportFunction("refreshPointData", initData.getPointPlans.bind(initData)); exportFunction("getCompleteTipStorage", initData.getCompleteTipStorage.bind(initData)); exportFunction("saveCompleteTipStorage", initData.saveCompleteTipStorage.bind(initData));
    function getAwards(campaign_id){ return fetch( `/api/loyalty-server/campaigns/${campaign_id}/participate`, { method: 'POST', } ); } exportFunction("getAwards", getAwards);
    function getListData() { return fetch( "\/api\/loyalty-server\/tier-details" ).then((response) => response.json()); } exportFunction('getListData', getListData); class LoyaltyBenefitsList extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback() { this.setupAction_(); } setupAction_() { this.registerAction("refresh", (invocation) => { const { event } = invocation.args; this.refreshList_(event); }); } refreshList_(event) { SPZ.whenApiDefined(document.getElementById('loyalty-page-level-info')).then((levelRender) => { const benefits = levelRender.getData()[0]; const member_detail = levelRender.getData()[1]; SPZ.whenApiDefined(this.element.querySelector('.loyalty-level-benefits-list__render')).then((api) => { api.render({ tier_detail: benefits.tier_details[event.index], member_detail }) }) }); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-level-benefits-list", LoyaltyBenefitsList); class SpzCustomLoyaltyModal extends SPZ.BaseElement { constructor(element) { super(element); this.container = document.querySelector( this.element.dataset.container ? this.element.dataset.container : "#loyalty-app__panel .loyalty-app__panel-body" ); } buildCallback() { this.setupAction_(); if (this.moved) return; this.moved = true; const originNode = this.container.querySelector(`:scope > #${this.element.id}`) if (originNode) { this.container.removeChild(originNode); } this.container.appendChild(this.element); } open_() { SPZCore.Dom.toggle(this.element, true); this.lockScroll_(); } lockScroll_() { this.container.classList.add("loyalty-lock-scroll"); } close_() { SPZCore.Dom.toggle(this.element, false); this.unlockScroll_(); } unlockScroll_() { this.container.classList.remove("loyalty-lock-scroll"); } setupAction_() { this.registerAction("open", (invocation) => { const { args } = invocation; this.open_(args); }); this.registerAction("close", () => { this.close_(); }); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-modal", SpzCustomLoyaltyModal); class SpzCustomLoyaltyInfoFormModal extends SpzCustomLoyaltyModal { constructor(element) { super(element); this.currentEdit = null; } open_({ type, data, title }) { if (!type) return; super.open_(); this.currentEdit = type; SPZ.whenApiDefined(this.element.querySelector('.loyalty-info-form-modal__render')).then((api) => { api.render({ title, attr: type, init_value: data }, true) }); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement( "spz-custom-loyalty-info-form-modal", SpzCustomLoyaltyInfoFormModal ); class SpzCustomLoyaltyEarnModal extends SpzCustomLoyaltyModal { constructor(element) { super(element); } open_({ index }) { SPZ.whenApiDefined(document.getElementById('loyalty-page-point-earn')).then((api) => { return api.getData(); }).then((data) => { const campaigns = data[1].campaigns; SPZ.whenApiDefined(this.element.querySelector('#loyalty-earn-detail-modal__render')).then((api) => { api.render(campaigns[index] || {}, true) super.open_(); }); }); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement( "spz-custom-loyalty-earn-modal", SpzCustomLoyaltyEarnModal ); function getAwards(campaign_id){ return fetch( `/api/loyalty-server/campaigns/${campaign_id}/participate`, { method: 'POST', } ); } exportFunction("getAwards", getAwards);

    No record yet

    No offers yet

    Loading...

    Discount code

    class SpzCustomLoyaltyPanel extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback() { SPZ.whenApiDefined(document.getElementById('loyalty-entry-config')).then((api) => { return api._getSectionConfig() }).then((config) => { this.config = config; // 校验是否可展示的页面 if (!this.checkDisplay_()) { throw new Error("Loyalty panel is not configured to display on this page."); } else { // 展示入口 this.element.hidden = false; } return SPZ.whenApiDefined(this.element.querySelector('#loyalty-panel-entry-render')); }).then((api) => { return api.render(); }).then(() => { this.setupAction_(); this.firstOpen = true; const launchIconContainer = this.element.querySelector(".loyalty-launch-icon"); this.openIcon = launchIconContainer.querySelector(".loyalty-launch__open"); this.closeIcon = launchIconContainer.querySelector(".loyalty-launch__close"); this.panel = this.element.querySelector(".loyalty-app__panel-body"); launchIconContainer.addEventListener("click", () => { if (this.openIcon.hidden) { this.close_(); } else { this.open_(); } }); this.viewport_ = this.getViewport(); this.registerAutoOpen_(); }).catch((error) => { console.error(error); }); } setupAction_() { this.registerAction("open", (invocation) => { const { event } = invocation.args; this.open_(event); }); this.registerAction("close", (invocation) => { const { event } = invocation.args; this.close_(event); }); } open_(page) { SPZCore.Dom.toggle(this.closeIcon, true); SPZCore.Dom.toggle(this.openIcon, false); SPZCore.Dom.toggle(this.panel, true); this.element.classList.remove("closed"); this.element.classList.add("opened"); if (this.firstOpen) { this.firstOpen = false; LoyaltyRouter.push("home"); const ele = document.getElementById('loyalty-async-get-data'); SPZ.whenApiDefined(ele).then((api) => { api.callFunction('refreshAllData') if (page && page !== 'home') { //打开后打开特定页面 LoyaltyRouter.push(page); } }); } if (this.viewport_.getWidth() < 960) { // 如果是在M端,则锁住外层滚动 this.viewport_.enterOverlayMode(); } } close_() { this.viewport_.leaveOverlayMode(); SPZCore.Dom.toggle(this.closeIcon, false); SPZCore.Dom.toggle(this.openIcon, true); SPZCore.Dom.toggle(this.panel, false); this.element.classList.remove("opened"); this.element.classList.add("closed"); } registerAutoOpen_() { const params = window.SPZUtils.Urls.parseQueryString(location.search); if(params.open_loyalty) { this.open_(params.open_loyalty) } } checkDisplay_() { const { show_pages } = this.config.settings; if (!show_pages || (show_pages.length === 1 && show_pages[0] === "all")) { // 1. 如果show_pages只有一个值且为'all',则表示在所有页面都显示 return true; } const currentPage = window.C_SETTINGS.meta.page.template_name; // 2. 如果show_pages中包含当前页面,则返回true if (show_pages.includes(currentPage)) { return true; } // promotions对应的页面 const promotionsPages = [ "flashsaleCollection", "couponCollection", "couponsCollection", "rebateCollection", "automaticCollection", "discountComplex", ]; // 3. 如果当前页面在promotions对应的页面中,并且show_pages中包含promotions,则返回true if (promotionsPages.includes(currentPage) && show_pages.includes("promotions")) { return true; } // account对应的页面 const accountPages = [ "customers/track", "customers/order", "customers/addresses", "customers/account", "customers/coupon", "order", "track", "addresses", "account", "coupon", ]; // 4. 如果当前页面在account对应的页面中,并且show_pages中包含account,则返回true if (accountPages.includes(currentPage) && show_pages.includes("account")) { return true; } // 5. 都没匹配到,则返回false return false; } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } static deferredMount() { return false; } } SPZ.defineElement("spz-custom-loyalty-panel", SpzCustomLoyaltyPanel); function matchDynamicRoute(path, routePattern) { // 正则表达式匹配动态路由,其中 :id 表示一个动态参数 const reg = new RegExp(`^${routePattern.replace(/:\w+/g, "([^/]+)")}$`); const match = path.match(reg); if (match) { // 去除第一个匹配项(整个匹配的字符串),剩下的就是参数数组 const params = match.slice(1); // 将参数与路由模式中的动态段对应起来,形成一个对象 const paramNames = routePattern.match(/:\w+/g) || []; const paramsObject = paramNames.reduce((acc, name, index) => { acc[name.slice(1)] = params[index]; return acc; }, {}); return paramsObject; } else { return null; } } class LoyaltyRouter extends SPZ.BaseElement { static routers = {}; static currIndex = -1; static stack = []; static deferredMount() { return false; } constructor(element) { super(element); } buildCallback() { const routePath = this.element.dataset.path; this.routePath = routePath; this.routeName = this.element.dataset.name; LoyaltyRouter.routers[routePath] = this; // default hidden,show when push SPZCore.Dom.toggle(this.element, false); this.element.classList.add("spz-custom-loyalty-router"); this.container = document.querySelector( "#loyalty-app__panel .loyalty-app__panel-body > .loyalty-app__panel-body-wrapper" ); this.action_ = SPZServices.actionServiceForDoc(this.element); if (this.moved) return; this.moved = true; this.container.appendChild(this.element); this.setupAction_(); } triggerRefreshEvent_(data) { this.showPageLoading_(); const event = SPZUtils.Event.create( this.win, "spz-custom-loyalty-router.refresh", data ); this.action_.trigger(this.element, "refresh", event); } triggerRenderEvent_(data) { const event = SPZUtils.Event.create( this.win, "spz-custom-loyalty-router.render", data ); this.action_.trigger(this.element, "render", event); } static push(path, options) { if (LoyaltyRouter.routers[path]) { LoyaltyRouter.stack.push(path); LoyaltyRouter.currIndex++; LoyaltyRouter.routers[path].render_(path, options); } else { // 进行动态路由匹配 const matchResult = Object.keys(LoyaltyRouter.routers).find((router) => { return matchDynamicRoute(path, router); }); if (matchResult) { const pathParams = matchDynamicRoute(path, matchResult); LoyaltyRouter.stack.push(matchResult); LoyaltyRouter.currIndex++; LoyaltyRouter.routers[matchResult].render_(matchResult, { ...options, params: { ...pathParams, ...(options.params || {}), }, }); } else { console.error(`Route '${path}' not found!`); } } } back() { LoyaltyRouter.stack.pop(); if (LoyaltyRouter.currIndex > 0) { LoyaltyRouter.currIndex--; // 返回时如果是到首页则展示首页出来,避免首页超长 if (LoyaltyRouter.currIndex === 0) { SPZCore.Dom.toggle(LoyaltyRouter.routers["home"].element, true); } const prevPage = LoyaltyRouter.routers[LoyaltyRouter.stack[LoyaltyRouter.currIndex]]; if (prevPage.element.classList.contains("loyalty-router-initialized")) { // 触发refresh事件 prevPage.triggerRefreshEvent_({}); } this.element.style.zIndex = 0; this.element.classList.add("loyalty-closed-page"); } } render_(path, options = {}) { const { pageTitle } = options; SPZCore.Dom.toggle(LoyaltyRouter.routers[path].element, true); if (LoyaltyRouter.currIndex > 0 && path !== "home") { this.showPageLoading_(); SPZCore.Dom.toggle(LoyaltyRouter.routers["home"].element, false); const template = document.getElementById( "loyalty-panel-with-back" ).content; let shadow = this.element.shadowRoot; if (!shadow) { shadow = this.element.attachShadow({ mode: "open" }); shadow.appendChild(template.cloneNode(true)); // 获取按钮元素并添加点击事件 const button = shadow.querySelector(".loyalty-panel__back"); button.addEventListener("click", () => { this.back(); }); const closeBtn = shadow.querySelector(".loyalty-panel__inner-close"); closeBtn.addEventListener("click", () => { SPZ.whenApiDefined( document.getElementById("loyalty-app__panel") ).then((apis) => { apis.close_(); }); }); } else if (shadow.querySelector("button")) { // 证明已经打开过 } this.setTitle_( shadow, pageTitle || LoyaltyRouter.routers[path].routeName ); } if (path === "home" && LoyaltyRouter.currIndex > 0) { // close all Object.keys(LoyaltyRouter.routers) .filter((path) => path !== "home") .forEach((path) => { LoyaltyRouter.routers[path].back(); }); } if (this.element.classList.contains("loyalty-router-initialized")) { // 触发refresh事件 this.triggerRefreshEvent_(options); } this.triggerRenderEvent_(options); this.element.style.zIndex = LoyaltyRouter.currIndex + 1; this.element.classList.remove("loyalty-closed-page"); this.element.classList.add("loyalty-router-initialized"); } showPageLoading_() { SPZCore.Dom.toggle(document.querySelector(".loyalty-global-loading"), true); } setupAction_() { // 用于动态设置页面标题 this.registerAction("setTitle", (invocation) => { const { args } = invocation; const {pageTitle} = args; this.setTitle_(this.element.shadowRoot, pageTitle); }); // 用于手动返回 this.registerAction("back", () => { this.back(); }); } setTitle_(shadow, pageTitle) { shadow.querySelector(".loyalty-panel__back-name").innerHTML = pageTitle; } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-router", LoyaltyRouter); class LoyaltyLink extends SPZ.BaseElement { constructor(element) { super(element); const params = Object.keys(element.dataset) .filter((key) => key.startsWith("param")) .reduce((acc, key) => { acc[key.replace("param", "").toLowerCase()] = element.dataset[key]; return acc; }, {}); this.clickEventHandler = (e) => { // 如果子元素点击包含禁止冒泡属性则不处理冒泡跳转 if (e.target.attributes['prevent-link']) { return; } if (element.dataset.path) { LoyaltyRouter.push(element.dataset.path, { pageTitle: element.dataset.name, params, }); } }; element.addEventListener("click", this.clickEventHandler); } unmountCallback() { this.element.removeEventListener("click", this.clickEventHandler); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-link", LoyaltyLink); class SpzCustomLoyaltyEvent extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback() { this.setupAction_(); this.action_ = SPZServices.actionServiceForDoc(this.element); this.origin = this.element.dataset.origin; const attributes = this.element.attributes; for (let i = 0; i < attributes.length; i++) { const attributeName = attributes[i].name; if (attributeName.startsWith('@event:')) { const eventName = attributeName.replace('@event:', ''); window.SPZUtils.Event.listen( window, eventName, (data) => { if(data.detail.origin !== this.origin) { this.triggerEvent_(`event:${eventName}`, data); } } ) } } } triggerEvent_(eventName, data) { const event = SPZUtils.Event.create( this.win, `spz-custom-loyalty-event.${eventName}`, data ); this.action_.trigger(this.element, eventName, event); } setupAction_() { this.registerAction("emit", (invocation) => { const { args } = invocation; const {eventName} = args; const e = window.SPZUtils.Event.create( window, eventName, args ); window.dispatchEvent(e); }); } isLayoutSupported(layout) { return layout === SPZCore.Layout.LOGIC; } } SPZ.defineElement("spz-custom-loyalty-event", SpzCustomLoyaltyEvent); class SpzCustomLoyaltyTrack extends SPZ.BaseElement { static observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { SPZ.whenApiDefined(entry.target).then((api) => { api.track_(); }) } }); }, { root: null, ratio: 0.6, }); constructor(element) { super(element); this.hasTrack = false; const { trackType, trackEvent_developer, ...dataset } = this.element.dataset; this.trackType = trackType; this.eventDeveloper = trackEvent_developer; const trackEventInfo = {}; Object.keys(dataset).forEach((dataKey) => { if (dataKey.startsWith('track')) { trackEventInfo[dataKey.replace('track', '').toLowerCase()] = dataset[dataKey]; } }) this.trackEventInfo = trackEventInfo; } mountCallback() { if (this.trackType === 'function_expose') { SpzCustomLoyaltyTrack.observer.observe(this.element); } else { this.element.addEventListener("click", () => { this.track_(); }); } } unmountCallback() { if (this.trackType === 'function_expose') { SpzCustomLoyaltyTrack.observer.unobserve(this.element); } else { this.element.removeEventListener("click", () => { this.track_(); }); } } track_() { if (this.hasTrack && this.trackType !== "click") return; SPZ.whenApiDefined(document.querySelector('.loyalty-init-data')).then((api) => { return api.callFunction('memberDetail') }).then((rst) => { // 默认游客 let membership_status = 'guest'; // 未登录时先赋值非会员 if (!!window.C_SETTINGS.customer.customer_id) membership_status = 'no_member'; // 是会员则上报会员名称 if (rst.member) membership_status = `member_${rst.current_tier.name}`; const {email_id} = window.SPZUtils.Urls.parseQueryString(window.location.search); const eventTypeMap = { 'function_expose': 'expose', 'click': 'click' } window.sa.track(this.trackType, { function_name: 'loyalty', plugin_name: 'loyalty', module_type: 'loyalty', module: 'apps', business_type: 'product_plugin', event_developer: this.eventDeveloper, event_type: eventTypeMap[this.trackType], event_info: JSON.stringify({ ...this.trackEventInfo, membership_status: membership_status, source_channels: email_id ? 'email' : 'Store', email_id: email_id }) }); if (this.trackType !== "click") this.hasTrack = true; }) } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER || layout === SPZCore.Layout.LOGIC; } } SPZ.defineElement("spz-custom-loyalty-track", SpzCustomLoyaltyTrack); class SpzCustomLoyaltyPoint extends SPZ.BaseElement { constructor(element) { super(element); this.value_ = element.getAttribute('value'); } buildCallback() { if (this.win.__loyalty_settings__) { this.win.__loyalty_settings__.then((settings) => { this.pointName_ = (settings.points_rule && settings.points_rule.points_name) || "Points"; this.render_(); }); } } mutatedAttributesCallback(mutations) { if (!SPZCore.Types.hasOwn(mutations, 'value')) { return; } this.value_ = mutations.value; this.render_(); } render_() { if (this.element.childElementCount > 0) { this.element.innerHTML = ''; } this.container_ = document.createElement("span"); this.container_.classList.add("loyalty-point"); this.container_.innerHTML = `${this.value_ !== null ? `${this.value_} ` : ''}${this.pointName_}`; this.element.appendChild(this.container_); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-point", SpzCustomLoyaltyPoint); class SpzCustomLoyaltySeeMore extends SPZ.BaseElement { constructor(element) { super(element); } buildCallback() { this.setupAction_(); } setupAction_() { this.registerAction("open", (invocation) => { this.render_(invocation.args); }); } render_(data) { SPZ.whenApiDefined(this.element.querySelector(':scope > ljs-render')).then((api) => { api.render(data, true) }); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-see-more", SpzCustomLoyaltySeeMore); class SpzCustomLoyaltyConfig extends SPZ.BaseElement { static configPromise = null; static getConfig() { // 如果已经有 Promise,返回它 if (this.configPromise) { return this.configPromise; } // 否则创建新的 Promise this.configPromise = new Promise((resolve) => { if (window.__loyalty_settings__) { window.__loyalty_settings__.then((allConfig) => { let newConfig = {}; Object.keys(allConfig.edit_config ?? {}).forEach((key) => { newConfig[key] = JSON.parse(allConfig.edit_config[key]); }); resolve(newConfig); }); } else { resolve({}); } }); return this.configPromise; } constructor(element) { super(element); this.value_ = element.getAttribute("value"); this.configType_ = element.dataset.configType; this.sectionName_ = element.dataset.sectionName; } buildCallback() { this.setupAction_(); } setupAction_() { this.registerAction("getConfig", () => { return this._getConfig(); }); } _getSectionConfig() { const emptyConfig = { id: '', settings: {}, }; return SpzCustomLoyaltyConfig.getConfig().then((config) => { if (!config) { console.error("Edit config is empty or not loaded."); return emptyConfig; } const sectionConfig = config[this.configType_]?.sections?.[this.sectionName_]; if (!sectionConfig) { console.log(`Section ${this.sectionName_} not found in config.`); return emptyConfig; } return { ...sectionConfig, id: this.sectionName_ }; }); } _getConfig() { return SpzCustomLoyaltyConfig.getConfig().then((config) => { if (!config) { console.error("Edit config is empty or not loaded."); return null; } return config[this.configType_] || {}; }); } isLayoutSupported(layout) { return layout === SPZCore.Layout.LOGIC; } } SPZ.defineElement("spz-custom-loyalty-config", SpzCustomLoyaltyConfig); const replaceImportValue = (content, dynamicObjects) => { const dynamic = { ...dynamicObjects, point_name: `<spz-custom-loyalty-point layout="container"></spz-custom-loyalty-point>`, } if (!content) { return ''; } let res = content; Object.keys(dynamic).forEach((key) => { res = res.replaceAll(`\{\{.${key}\}\}`, dynamic[key]); }); return res; }; class SpzCustomLoyaltyDynamicContent extends SPZ.BaseElement { constructor(element) { super(element); this.content = element.dataset.content; // 获取所有以data-dynamic-*开头的data属性 this.dynamicObjects = {}; const attributes = element.attributes; for (let i = 0; i < attributes.length; i++) { const attributeName = attributes[i].name; if (attributeName.startsWith('data-dynamic-')) { const key = attributeName.replace('data-dynamic-', ''); this.dynamicObjects[key] = attributes[i].value; } } } layoutCallback() { this.render_(); } mutatedAttributesCallback(mutations) { if (!SPZCore.Types.hasOwn(mutations, "data-content")) { return; } this.content = mutations['data-content']; this.render_(); } render_() { this.element.innerHTML = replaceImportValue(this.content, this.dynamicObjects); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement( "spz-custom-loyalty-dynamic-content", SpzCustomLoyaltyDynamicContent ); class SpzCustomLoyaltyQueryModal extends SPZ.BaseElement { constructor(element) { super(element); this.queryParam = element.dataset.queryParam; } static deferredMount() { return false; } buildCallback() { this.checkMember_(); this.setupAction_(); } checkMember_() { const params = window.SPZUtils.Urls.parseQueryString(window.location.search); if (params[this.queryParam]) { if (!window.C_SETTINGS.customer?.customer_id) { window.location.href="\/account\/login"; } else { // 已登陆 SPZ.whenApiDefined(document.getElementById('loyalty-async-get-data')).then((api) => { return api.callFunction('memberDetail'); }).then((data) => { if (data.member) { // 已入会才弹窗 SPZ.whenApiDefined(this.element.querySelector(':scope>ljs-lightbox')).then((modal) => { // 用于在弹窗打开前执行一些操作 this.beforeOpen_(params[this.queryParam], params); modal.open(); }); } else { // 未入会直接移除参数 this.removeQuery_(); } }); } } } setupAction_() { this.registerAction("close", () => { this.removeQuery_(); }); } // 用于在弹窗打开前执行一些操作 beforeOpen_() {} removeQuery_() { const currentUrl = window.location.href; // 使用 URLSearchParams 解析查询参数 const url = new URL(currentUrl); const params = SPZUtils.Urls.addOrReplaceParams(window.location.href, {[this.queryParam]: null}); window.history.replaceState( null, '', `${url.origin}${params}` ); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } } SPZ.defineElement("spz-custom-loyalty-query-modal", SpzCustomLoyaltyQueryModal);

    Cancel membership

    You've successfully cancelled.