• 로그인
  • 계정 만들기
  • Global Sites
  • France
  • Japanese
  • Netherlands
  • 집
  • 신상품
      매주 토요일 신상품 (11월 15일) 매주 토요일 신상품 (11월 8일) 매주 토요일 신상품 (11월 1일) 매주 토요일 신상품 (10월 25일) 매주 토요일 신상품 (10월 18일) 매주 토요일 신상품 (10월 11일) 매주 토요일 신상품 (10월 4일) 매주 토요일 신상품 (9월 27일) 매주 토요일 신상품 (9월 20일) 매주 토요일 신상품 (9월 13일)
    52% 저장 52% 저장

    와시 테이프 - 80mm*200cm 반려동물 힐링 라이프 시리즈 귀여운 강아지 캐릭터

    에서 ₩2,136 ₩4,449 +4
  • 베스트셀러
  • 제품 카테고리
    • 장식 스티커 PET 스티커 와시 스티커 스티커북 기타 소재 스티커
    • 테이프 PET 테이프 와시 테이프
    • 저널링 자료 재질지 & 메모패드(접착 없음) 스티커 노트 저널링 키트
    • 저널링 도구 용품 가위 도구 지우개 & 수정테이프 접착제 및 접착 테이프 클립 서표 측정 도구
    • 스탬프 및 잉크패드 도장 잉크 패드 스텐실 & 템플릿 실링 왁스
    • 노트 바인더 다이어리
    • 수납 용품 필통 보관함 | 보관가방 카드백 자
    • 필기구 마커펜 & 형광펜 & 라인 마커 젤펜 브러쉬펜 수채화 펜과 수채화 페인트 볼펜 기계식 연필
    • 기타 제품 데스크탑 장식품 달력 키 홀더 인사말 카드 휴대폰 액세서리
    • 기프트 카드
    • ATC 공급품
  • 테마 카테고리
    • 크리스마스 🎄
    • 🌼 꽃과 식물
    • 캐릭터 이미지
    • 여행 🌴 & 풍경
    • 프레임 및 필름
    • 나비 🦋
    • 커피 ☕
    • 창문 & 문
    • 별이 빛나는 하늘 🌌 & 달
    • 동물 & 곤충
    • 건물 🏠 & 가구
    • 다크 & 고딕
    • 🌊 바다 & 물
    • 텍스트 & 숫자
    • 중국 스타일 🐼
    • 파티🎉 & 조명
    • 레이스
    • 수채화 & 얼룩
    • 라벨 & 메모패드
    • 병
    • 유화
    • 구름 & 거품
    • 용🐉
    • 스팀펑크
    • 사계절 ❄️
    • 마법
    • 할로윈 💀
    • 🥐 음식 & 과일 🍇
    • 엘프 🧚‍♀️
    • 이국적인 풍습 & 소수 민족
    • 문신
    • 우주비행사 👩🏻‍🚀
    • 보트 ⛵
    • 캠핑 ⛺
    • 직물 질감 및 천 예술
    • 변덕
  • 브랜드 카테고리
    • MOODTAPE
    • YIER
    • OKMT
    • Ding Ding
    • Journalsay
  • 모든 제품
  • 블로그
  • 더 많은 링크
  • 집
  • 신상품
      매주 토요일 신상품 (11월 15일) 매주 토요일 신상품 (11월 8일) 매주 토요일 신상품 (11월 1일) 매주 토요일 신상품 (10월 25일) 매주 토요일 신상품 (10월 18일) 매주 토요일 신상품 (10월 11일) 매주 토요일 신상품 (10월 4일) 매주 토요일 신상품 (9월 27일) 매주 토요일 신상품 (9월 20일) 매주 토요일 신상품 (9월 13일)
    52% 저장 52% 저장

    와시 테이프 - 80mm*200cm 반려동물 힐링 라이프 시리즈 귀여운 강아지 캐릭터

    에서 ₩2,136 ₩4,449 +4
  • 베스트셀러
  • 제품 카테고리
    • 장식 스티커 PET 스티커 와시 스티커 스티커북 기타 소재 스티커
    • 테이프 PET 테이프 와시 테이프
    • 저널링 자료 재질지 & 메모패드(접착 없음) 스티커 노트 저널링 키트
    • 저널링 도구 용품 가위 도구 지우개 & 수정테이프 접착제 및 접착 테이프 클립 서표 측정 도구
    • 스탬프 및 잉크패드 도장 잉크 패드 스텐실 & 템플릿 실링 왁스
    • 노트 바인더 다이어리
    • 수납 용품 필통 보관함 | 보관가방 카드백 자
    • 필기구 마커펜 & 형광펜 & 라인 마커 젤펜 브러쉬펜 수채화 펜과 수채화 페인트 볼펜 기계식 연필
    • 기타 제품 데스크탑 장식품 달력 키 홀더 인사말 카드 휴대폰 액세서리
    • 기프트 카드
    • ATC 공급품
  • 테마 카테고리
    • 크리스마스 🎄
    • 🌼 꽃과 식물
    • 캐릭터 이미지
    • 여행 🌴 & 풍경
    • 프레임 및 필름
    • 나비 🦋
    • 커피 ☕
    • 창문 & 문
    • 별이 빛나는 하늘 🌌 & 달
    • 동물 & 곤충
    • 건물 🏠 & 가구
    • 다크 & 고딕
    • 🌊 바다 & 물
    • 텍스트 & 숫자
    • 중국 스타일 🐼
    • 파티🎉 & 조명
    • 레이스
    • 수채화 & 얼룩
    • 라벨 & 메모패드
    • 병
    • 유화
    • 구름 & 거품
    • 용🐉
    • 스팀펑크
    • 사계절 ❄️
    • 마법
    • 할로윈 💀
    • 🥐 음식 & 과일 🍇
    • 엘프 🧚‍♀️
    • 이국적인 풍습 & 소수 민족
    • 문신
    • 우주비행사 👩🏻‍🚀
    • 보트 ⛵
    • 캠핑 ⛺
    • 직물 질감 및 천 예술
    • 변덕
  • 브랜드 카테고리
    • MOODTAPE
    • YIER
    • OKMT
    • Ding Ding
    • Journalsay
  • 모든 제품
  • 블로그
  • 더 많은 링크

  • 로그인
  • 계정 만들기
  • Global Sites
  • France
  • Japanese
  • Netherlands
  • 집
  • 신상품
    • 신상품
    • 매주 토요일 신상품 (11월 15일)
    • 매주 토요일 신상품 (11월 8일)
    • 매주 토요일 신상품 (11월 1일)
    • 매주 토요일 신상품 (10월 25일)
    • 매주 토요일 신상품 (10월 18일)
    • 매주 토요일 신상품 (10월 11일)
    • 매주 토요일 신상품 (10월 4일)
    • 매주 토요일 신상품 (9월 27일)
    • 매주 토요일 신상품 (9월 20일)
    • 매주 토요일 신상품 (9월 13일)
  • 베스트셀러
  • 제품 카테고리
    • 제품 카테고리
    • 장식 스티커
      • 장식 스티커
      • PET 스티커
      • 와시 스티커
      • 스티커북
      • 기타 소재 스티커
    • 테이프
      • 테이프
      • PET 테이프
      • 와시 테이프
    • 저널링 자료
      • 저널링 자료
      • 재질지 & 메모패드(접착 없음)
      • 스티커 노트
      • 저널링 키트
    • 저널링 도구 용품
      • 저널링 도구 용품
      • 가위 도구
      • 지우개 & 수정테이프
      • 접착제 및 접착 테이프
      • 클립
      • 서표
      • 측정 도구
    • 스탬프 및 잉크패드
      • 스탬프 및 잉크패드
      • 도장
      • 잉크 패드
      • 스텐실 & 템플릿
      • 실링 왁스
    • 노트
      • 노트
      • 바인더
      • 다이어리
    • 수납 용품
      • 수납 용품
      • 필통
      • 보관함 | 보관가방
      • 카드백
      • 자
    • 필기구
      • 필기구
      • 마커펜 & 형광펜 & 라인 마커
      • 젤펜
      • 브러쉬펜
      • 수채화 펜과 수채화 페인트
      • 볼펜
      • 기계식 연필
    • 기타 제품
      • 기타 제품
      • 데스크탑 장식품
      • 달력
      • 키 홀더
      • 인사말 카드
      • 휴대폰 액세서리
    • 기프트 카드
    • ATC 공급품
  • 테마 카테고리
    • 테마 카테고리
    • 크리스마스 🎄
    • 🌼 꽃과 식물
    • 캐릭터 이미지
    • 여행 🌴 & 풍경
    • 프레임 및 필름
    • 나비 🦋
    • 커피 ☕
    • 창문 & 문
    • 별이 빛나는 하늘 🌌 & 달
    • 동물 & 곤충
    • 건물 🏠 & 가구
    • 다크 & 고딕
    • 🌊 바다 & 물
    • 텍스트 & 숫자
    • 중국 스타일 🐼
    • 파티🎉 & 조명
    • 레이스
    • 수채화 & 얼룩
    • 라벨 & 메모패드
    • 병
    • 유화
    • 구름 & 거품
    • 용🐉
    • 스팀펑크
    • 사계절 ❄️
    • 마법
    • 할로윈 💀
    • 🥐 음식 & 과일 🍇
    • 엘프 🧚‍♀️
    • 이국적인 풍습 & 소수 민족
    • 문신
    • 우주비행사 👩🏻‍🚀
    • 보트 ⛵
    • 캠핑 ⛺
    • 직물 질감 및 천 예술
    • 변덕
  • 브랜드 카테고리
    • 브랜드 카테고리
    • MOODTAPE
    • YIER
    • OKMT
    • Ding Ding
    • Journalsay
  • 모든 제품
  • 블로그
  • 로그인
  • 계정 만들기
  • (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(); })();
    집  /  Blog

    Blog

    Journalsay 11.11 세일 2025 - 저널링 용품 대규모 할인 Journalsay 11.11 세일 2025 - 저널링 용품 대규모 할인
    Journalsay 11.11 세일 2025 - 저널링 용품 대규모 할인 ~에 의해 journalsay-kr
    Journalsay ATC 카드 챌린지에 참여하고 $10 기프트 카드를 받아가세요! ~에 의해 Journalsay
    Journaling Fest 2025 세일: 52% 할인 + 추가 할인! ~에 의해 Journalsay
    Handmade wooden retro storage box: not only a storage tool, but also a work of art! ~에 의해 journalsay
    🦋 Butterfly themed scrapbook set 🌸 a combination of fantasy and glamour ✨ ~에 의해 Journalsay
    🎄Would you like such a Christmas gift? ---Journaling materials ~에 의해 Journalsay
    🎉 FIRST DAY SALE: Grab Up to 63% Off Now!! 🎉 ~에 의해 Journalsay
    Easy card making IDEAS, TIPS, TRICKS and DESIGNS | Journalsay PET stickers and more❤ ~에 의해 Journalsay
    Journaling🌟14 basic tape usage skills that beginners must learn ~에 의해 Journalsay

    오늘 구독하시면 5% 할인 세일 소식을 가장 먼저 받아보실 수 있습니다.

    유효한 이메일 주소를 입력하세요.
    당신의 이메일 주소를 입력하십시오.
    구독해 주셔서 감사합니다.

    우리는 받아들입니다

    • American Express
    • Apple Pay
    • Dankort
    • Diners Club
    • Discover
    • Google Pay
    • JCB
    • Klarna
    • Maestro
    • PayPal
    • Visa
    • Mastercard

    우리는 받아들입니다

    • American Express
    • Apple Pay
    • Dankort
    • Diners Club
    • Discover
    • Google Pay
    • JCB
    • Klarna
    • Maestro
    • PayPal
    • Visa
    • Mastercard

    연락하세요

    • journalsay04@gmail.com

    연락하세요

    • journalsay04@gmail.com

    팔로우

    팔로우

    고객 관리

    • 회사 소개
    • 문의하기
    • 주문 확인
    • Journalsay 클럽
    • Affiliat
    • Trustpilot
    • 자주 묻는 질문

    고객 관리

    • 회사 소개
    • 문의하기
    • 주문 확인
    • Journalsay 클럽
    • Affiliat
    • Trustpilot
    • 자주 묻는 질문

    도움말 및 지원

    • 배송 세부 정보
    • 반품 정책
    • 결제 정책
    • 개인정보 보호정책
    • 서비스 약관
    • 제품 피드백

    도움말 및 지원

    • 배송 세부 정보
    • 반품 정책
    • 결제 정책
    • 개인정보 보호정책
    • 서비스 약관
    • 제품 피드백
    © 2025 journalsay-kr

    카트

    장바구니가 비어 있습니다.
    장바구니보기
    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);

    아직 기록 없음

    아직 제안 없음

    로딩 중...

    할인 코드

    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);

    회원 탈퇴

    성공적으로 탈퇴했습니다.