1let pageViewRecorded = false; 2 3Vue.component('cua-widget', { 4 template: ` 5 <div ref="widget"> 6 <div v-show="!allImagesLoaded" v-html="createCuaSkeletonTemplate(currentCuaSpace)"></div> 7 <div v-show="allImagesLoaded"> 8 <div :id="'swiper-container-' + currentCuaSpace" v-if="cuaType === 'swiper'" class="swiper"> 9 <div class="swiper-wrapper"> 10 <a :href="generatedUrl(cua)" v-for="cua in cuaList" :key="cua.cuaId" :data-cua-id="cua.cuaId" :data-space-id="cua.cuaSpaceId" class="swiper-slide"> 11 <div v-html="createCuaTemplate(spaceInfo, cua)"></div> 12 </a> 13 </div> 14 <div v-show="cuaList?.length > 1" class="pagination-wrap"> 15 <div class="swiper-pagination"></div> 16 </div> 17 </div> 18 <div v-else-if="cuaType === 'static'" :class="adStaticContainerClass"> 19 <a :href="generatedUrl(cua)" v-for="cua in cuaList" :key="cua.cuaId" class="content-wrap" :data-cua-id="cua.cuaId" :data-space-id="cua.cuaSpaceId"> 20 <div v-html="createCuaTemplate(spaceInfo, cua)"></div> 21 </a> 22 </div> 23 <div v-else-if="cuaType === 'popup'" style="height: 0;"> 24 <button ref="triggerBtn" class="btn" type="button" data-bs-toggle="offcanvas" data-bs-target="#banner-offcanvas" 25 aria-controls="offcanvasBottom" style="width: 0; height: 0;"></button> 26 <div class="offcanvas offcanvas-bottom" data-bs-scroll="false" tabindex="-1" id="banner-offcanvas" aria-labelledby="offcanvasBottomLabel" style="height: auto;"> 27 <div :id="'swiper-container-' + currentCuaSpace" class="swiper h-100 m-0"> 28 <div class="swiper-wrapper"> 29 <a :href="generatedUrl(cua)" v-for="cua in cuaList" :key="cua.cuaId" :data-cua-id="cua.cuaId" :data-space-id="cua.cuaSpaceId" class="offcanvas-body cursor p-0 swiper-slide"> 30 <div v-html="createCuaTemplate(spaceInfo, cua)"></div> 31 </a> 32 </div> 33 <div v-show="cuaList?.length > 1" class="pagination-wrap"> 34 <div class="swiper-pagination"></div> 35 </div> 36 </div> 37 <div class="offcanvas-footer d-flex justify-content-between"> 38 <button @click.stop="hidePopup($event)" type="button" class="cpt-sub-title p-3" data-bs-dismiss="offcanvas">ì¼ì£¼ì¼ ëì ë³´ì§ ì기</button> 39 <button @click.stop="closePopup($event)" type="button" class="p-3" data-bs-dismiss="offcanvas" aria-label="Close">ë«ê¸°</button> 40 </div> 41 </div> 42 </div> 43 </div> 44 </div> 45 `, 46 data () { 47 return { 48 swiper: {}, 49 cuaList: [], 50 spaceInfo: null, 51 useSwiper: null, 52 observer: null, 53 currentCuaSpace: null, 54 swiperLoopCondition: null, 55 allImagesLoaded: false, 56 isInViewport: false, 57 cuaType: null, 58 }; 59 }, 60 computed: { 61 generatedUrl() { 62 return (cua) => { 63 if (!cua.redirectUrl) return '#'; 64 65 const baseUrl = `${_webRootUrl}/inbound/cua`; 66 const additionalParams = this.generateUrlParameter(cua); 67 68 if (!additionalParams) { 69 return baseUrl; 70 } 71 return `${baseUrl}?${additionalParams}`; 72 } 73 }, 74 adStaticContainerClass () { 75 return ''; 76 }, 77 }, 78 mounted () { 79 const adContainer = this.$refs.widget; 80 this.currentCuaSpace = adContainer.getAttribute('data-widget-code');
81 if (this.currentCuaSpace) this.getSpaceInfo(this.currentCuaSpace); 82 }, 83 beforeDestroy () { 84 this.observer?.disconnect(); 85 }, 86 methods: { 87 hidePopup(event) { 88 event.preventDefault(); 89 event.stopPropagation(); 90 setCookie('hidePopup', 'true', 10080); 91 this.deleteSlide(); 92 }, 93 closePopup(event) { 94 event.preventDefault(); 95 this.deleteSlide(); 96 }, 97 deleteSlide() { 98 const swiperGroup = `swiper-group-${this.currentCuaSpace}`; 99 if (this.swiper[swiperGroup]) { 100 this.swiper[swiperGroup].autoplay.stop(); 101 } 102 }, 103 initObserver () { 104 this.observer = new IntersectionObserver(this.handleIntersect, { 105 root: null, 106 threshold: 0.1, 107 }); 108 if (this.$refs.widget) this.observer.observe(this.$refs.widget); 109 }, 110 getSpaceInfo (cuaSpaceCode) { 111 _get(`${_campinfoServer_}/info/v3/cua/space`, { 112 cuaSpaceCode: cuaSpaceCode 113 }, (res) => { 114 if (res.status === RestResponseStatus.OK) { 115 this.spaceInfo = res.data.cuaSpace; 116 this.determineRenderingMethod(cuaSpaceCode); 117 this.getCuaList(cuaSpaceCode); 118 } 119 }); 120 }, 121 122 /** 123 * ë ëë§ ë°©ì ê²°ì 124 * 125 * @param cuaSpaceCode 126 */ 127 determineRenderingMethod (cuaSpaceCode) { 128 switch (cuaSpaceCode) { 129 case 'MAIN_PAGE_BOTTOM': 130 if (getCookie('hidePopup')) return null; 131 this.useSwiper = true; 132 this.cuaType = 'popup'; 133 break; 134 default: 135 this.useSwiper = true; 136 this.cuaType = 'swiper'; 137 break; 138 } 139 }, 140 141 /** 142 * ì§ë©´ì ê²ì¬ ì¤ì¸ CUA ëª©ë¡ 143 * 144 * @param cuaSpaceCode 145 */ 146 getCuaList (cuaSpaceCode) { 147 _get(`${_campinfoServer_}/info/v3/cua/list`, { 148 cuaSpaceCode: cuaSpaceCode, 149 isInProgress: true, 150 isActivePeriod: true, 151 cuaAudience: this.getCuaAudience(), 152 sortIndex: 'sortOrder', 153 sortType: 'ASC' 154 }, (res) => { 155 if (res.status === RestResponseStatus.OK) { 156 // imageUrl ëë bannerImageUrlì´ ìë ê´ê³ ë§ íí°ë§ 157 this.cuaList = res.data.cuaList.filter(cua => cua.imageUrl || cua.bannerImageUrl); 158 this.swiperLoopCondition = this.cuaList.length > 1; 159 160 if (this.cuaList.length > 0) { 161 if (this.useSwiper) this.createSwiper(); 162 else this.initObserver(); 163 164 const cuaIds = this.cuaList.map(item => item.cuaId); 165 const cuaSpaceIds = this.cuaList.map(item => item.cuaSpaceId); 166 167 if (!pageViewRecorded) { 168 this.statCua(cuaIds, cuaSpaceIds, 'pageView'); 169 pageViewRecorded = true; 170 } 171 172 if (cuaSpaceCode === 'MAIN_PAGE_BOTTOM' && !getCookie('hidePopup')) { 173 this.$nextTick(() => { 174 this.$refs.triggerBtn.click(); 175 }); 176 } 177 } 178 179 this.allImagesLoaded = true; 180 } 181 }); 182 }, 183 184 getCuaAudience () { 185 return memberManager.isMember ? 'MEMBER' : 'GUEST'; 186 }, 187 188 /** 189 * ì¤ìì´í¼ ìµì ì¸í 190 * @param cuaSpaceCode 191 * @returns 192 */ 193 getSwiperOptions (cuaSpaceCode) { 194 switch (cuaSpaceCode) { 195 case 'MAIN_PAGE_TOP': 196 return { 197 autoplay: { delay: 4000, disableOnInteraction: false }, 198 pagination: { el: '.swiper-pagination', clickable: true, type: "fraction" }, 199 loop: this.swiperLoopCondition, 200 spaceBetween: 16, 201 slidesPerView: 'auto', 202 centeredSlides: true, 203 }; 204 default: 205 return { 206 autoplay: { delay: 6000, disableOnInteraction: false }, 207 pagination: { el: '.swiper-pagination', type: 'fraction', clickable: true }, 208 loop: this.swiperLoopCondition, 209 }; 210 } 211 }, 212 213 /** 214 * ì¤ìì´í¼ ìì± 215 */ 216 createSwiper () { 217 const swiperGroup = `swiper-group-${this.currentCuaSpace}`; 218 const swiperId = `#swiper-container-${this.currentCuaSpace}`;
219 const swiperOptions = this.getSwiperOptions(this.currentCuaSpace); 220 221 setTimeout(() => { 222 this.swiper[swiperGroup] = new Swiper(swiperId, { 223 ...swiperOptions, 224 threshold: 10, 225 observer: true, 226 lazy: false, 227 autoHeight: true, 228 on: { 229 init: () => { this.initObserver(); }, 230 slideNextTransitionEnd: () => { 231 if (this.isInViewport && this.swiper[swiperGroup]) { 232 const activeIndex = this.swiper[swiperGroup].realIndex; 233 this.statCua([this.cuaList[activeIndex].cuaId], [this.cuaList[activeIndex].cuaSpaceId], 'cuaView'); 234 } 235 } 236 } 237 }); 238 }, 200); 239 }, 240 241 /** 242 * ê´ê³ 목ë¡ì´ ë¡ëë기 ì íìí skeleton ui 243 * 244 * @param code 245 * @returns {string} 246 */ 247 createCuaSkeletonTemplate (code) { 248 const spaceInfo = this.spaceInfo; 249 let aspectRatioStyle = 'padding-top: 19.14%;'; 250 251 if (spaceInfo && spaceInfo.bannerWidth > 0 && spaceInfo.bannerHeight > 0) { 252 const ratio = (spaceInfo.bannerHeight / spaceInfo.bannerWidth) * 100; 253 aspectRatioStyle = `padding-top: ${ratio}%;`; 254 } 255 256 if (code === 'MAIN_PAGE_TOP') { 257 return ` 258 <div class="skeleton-ui"> 259 <div class="skeleton-item"><div class="skeleton skeleton-image"></div></div> 260 <div class="swiper-skeleton-pagination"> 261 <div class="skeleton skeleton-dot"></div> 262 <div class="skeleton skeleton-dot"></div> 263 <div class="skeleton skeleton-dot"></div> 264 </div> 265 </div>`; 266 } 267 268 if (code === 'MAIN_PAGE_BOTTOM') return `<div></div>`; 269 270 return ` 271 <div class="skeleton-ui" style="width: 100%; ${aspectRatioStyle} position: relative;"> 272 <div class="skeleton-item" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0;"> 273 <div class="skeleton skeleton-image" style="width: 100%; height: 100%;"></div> 274 </div> 275 </div>`; 276 }, 277 278 /** 279 * ê´ê³ í í릿 ìì± 280 * 281 * @param spaceInfo 282 * @param templateType 283 * @param cua 284 * @param templateContent 285 * @returns {string} 286 */ 287 createCuaTemplate (spaceInfo, cua) { 288 const { bannerWidth, bannerHeight } = spaceInfo; 289 const bannerImageUrl = cua.bannerImageUrl || ''; 290 291 // imageUrl ëë bannerImageUrlì´ ìì¼ë©´ íìíì§ ìì 292 if (!cua.imageUrl && !bannerImageUrl) { 293 return ''; 294 } 295 296 const imageUrl = cua.imageUrl 297 ? `${_imageServerUrl_}${cua.imageUrl}?f=webp&q=100` 298 : (bannerImageUrl.startsWith('http') ? bannerImageUrl : `${_staticResourceServer_}${bannerImageUrl}`); 299 300 let style = 'width: 100%; height: auto; object-fit: cover;'; 301 if (bannerWidth && bannerHeight && bannerWidth > 0 && bannerHeight > 0) { 302 style += ` aspect-ratio: ${bannerWidth} / ${bannerHeight};`; 303 } 304 305 return `<img src="${imageUrl}" alt="${cua.cuaName}" style="${style}" />`; 306 }, 307 /** 308 * URL ìì± 309 * 310 * @param cua 311 * @returns {string} 312 */ 313 generateUrlParameter (cua) { 314 const parsedUrlParameters = cua.redirectUrl === 'LOGIN' ? [] : JSON.parse(cua.urlParameter || '[]'); 315 const cuaData = window.cuaData; 316 const clientParams = {}; 317 const systemParams = {}; 318 319 parsedUrlParameters.filter(param => param.type === 'client').forEach(item => { 320 const { key, value } = item; 321 if (value.includes('${')) { 322 const placeholderKey = value.match(/\${(.*?)}/)[1]; 323 if (cuaData.hasOwnProperty(placeholderKey)) { clientParams[key] = cuaData[placeholderKey]; } 324 } else { clientParams[key] = value; } 325 }); 326 parsedUrlParameters.filter(param => param.type === 'system').forEach(item => {
327 const key = item.value.match(/\${(.*?)}/)[1]; 328 systemParams[key] = item.key; 329 }); 330 331 const clientParamsString = Object.entries(clientParams) 332 .filter(([key, value]) => key && value) 333 .map(([key, value]) => `${key}=${value}`) 334 .join('&'); 335 336 const systemParameter = { 337 cuaId: cua.cuaId, 338 referer: document.referrer, 339 destination: this.resolveDestination(cua), 340 cuaSpaceId: cua.cuaSpaceId, 341 friendKey: memberManager.member.memberInfo ? memberManager.member.memberInfo.friendCode : null, 342 actionType: 'click', 343 cuid: CUID, 344 isApp: clientEnv.isApp, 345 isMobile: clientEnv.isMobile, 346 ip: clientEnv.additional.network.ip, 347 requireInfos: systemParams, 348 isExternalLink: !!cua.isExternalLink, 349 }; 350 const encodedSystemParams = encodeURIComponent(this.aes128Encode(CUA_KEY, CUA_IV, JSON.stringify(systemParameter))); 351 const systemParamsString = `x=${encodedSystemParams}`; 352 353 const allParams = []; 354 if (clientParamsString) { 355 allParams.push(clientParamsString); 356 } 357 allParams.push(systemParamsString); // Always add system params 358
359 return allParams.join('&'); 360 }, 361 362 resolveDestination (cua) { 363 if (cua.redirectUrl === 'LOGIN') { 364 // CUA tracking endpoint(/inbound/cua)ë ë¡ê·¸ì¸ í ë¤ì ì§ì íë©´ 365 // ëì¼í LOGIN ê´ê³ 를 ì¬ì¤íí´ ë¡ê·¸ì¸ íì´ì§ë¡ ë°ë³µ ì´ëíë¤. 366 // ê´ê³ 를 í´ë¦í ìë íì´ì§ë¥¼ destinationì¼ë¡ ë겨 tracking endpoint 367 // ëì ìë íì´ì§ë¡ ë³µê·ìí¨ë¤. 368 const currentUrl = window.location.pathname + window.location.search; 369 return authManager.getSnsLoginUrl({ destinationUri: currentUrl }); 370 } 371 return !cua.isExternalLink ? `${_webRootUrl}${cua.redirectUrl}` : cua.redirectUrl; 372 }, 373 374 /** 375 * ë·°í¬í¸ì CUA ììì´ êµì°¨ëìì ë í¸ì¶ 376 * @param entries 377 */ 378 handleIntersect (entries) { 379 const shownCuaIds = new Set(); 380 const shownCuaSpaceIds = []; 381 382 entries.forEach(entry => { 383 if (entry.isIntersecting && this.cuaList.length > 0) { 384 this.isInViewport = true; 385 if (this.useSwiper) { 386 let targetElement = entry.target.querySelectorAll('[data-cua-id]'); 387 if (targetElement) { 388 const swiperGroup = `swiper-group-${this.currentCuaSpace}`; 389 if (this.swiper[swiperGroup]) { 390 const activeIndex = this.swiper[swiperGroup].realIndex; 391 shownCuaIds.add([this.cuaList[activeIndex].cuaId]); 392 shownCuaSpaceIds.push(this.cuaList[activeIndex].cuaSpaceId); 393 } 394 } 395 } else { 396 const cuaElements = entry.target.querySelectorAll('[data-cua-id]'); 397 cuaElements.forEach(cuaElement => { 398 const cuaId = cuaElement.getAttribute('data-cua-id'); 399 const cuaSpaceId = cuaElement.getAttribute('data-space-id'); 400 shownCuaIds.add(cuaId); 401 shownCuaSpaceIds.push(cuaSpaceId); 402 }); 403 } 404 405 if (shownCuaIds.size > 0) { 406 this.statCua(Array.from(shownCuaIds).map(Number), shownCuaSpaceIds, 'cuaView'); 407 } 408 } else { 409 this.isInViewport = false; 410 } 411 }); 412 }, 413 414 /** 415 * ë·° íë¼ë¯¸í° ìì± 416 * 417 * @param actionType 418 * @returns {{actionType, cuid: *, cuaIds: (string|*)[], ip: *}} 419 */ 420 generateViewParam (actionType) { 421 return { 422 cuaIds: this.cuaList.map(item => item.cuaId), 423 actionType: actionType, 424 cuid: CUID, 425 ip: clientEnv.additional.debugInfo.cloudFlare.ip, 426 }; 427 }, 428 429 /** 430 * ìí¸í 431 * 432 * @param secretKey 433 * @param iv 434 * @param data 435 * @returns {string} 436 */ 437 aes128Encode (secretKey, iv, data) { 438 const cipher = CryptoJS.AES.encrypt(data, CryptoJS.enc.Utf8.parse(secretKey), { 439 iv: CryptoJS.enc.Utf8.parse(iv), 440 padding: CryptoJS.pad.Pkcs7, 441 mode: CryptoJS.mode.CBC, 442 }); 443 return cipher.toString(); 444 }, 445 446 /** 447 * CUA ì§ê³ 448 */ 449 statCua (cuaIds, cuaPlaceIds, actionType) { 450 _post(`/rest/domain/cua/event/view`, { 451 cuaIds: cuaIds, 452 cuaSpaceIds: cuaPlaceIds, 453 actionType: actionType, 454 cuid: CUID, 455 ip: clientEnv.additional.network.ip, 456 }, (res) => { 457 if (res.status === RestResponseStatus.OK) { 458 // Success 459 } 460 }); 461 }, 462 } 463});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.