PageSourceSearch

https://shokz.co.kr/js/tracking/tracking-common.js?ver=2609221605

js shokz.co.kr collected 2026-09-30 06:27:45 UTC 13,746 bytes, 414 lines download raw bytes

1//埋点的公共函数文件
2
3// 下拉产品分类产品导航 埋点标识切换
4const product_index_change = {
5    "index_navigation_tws":"완전 무선 시리즈",
6    "index_navigation_sports":"스포츠 시리즈",
7    "index_navigation_swim":"수영 시리즈",
8    "index_navigation_comm":"커뮤니케이션 시리즈",
9    "index_navigation_accessory":"액세서리",
10    "product_collection_all":"ALL",
11    "index_best":"인기",
12};
13
14//产品卡片旧的item_list_id转换成新的
15const product_list_id_change = {
16    "product_collection_all":"ALL",
17    "product_collection_tws":"완전 무선 시리즈",
18    "product_collection_sports":"스포츠 시리즈",
19    "product_collection_swim":"수영 시리즈",
20    "product_collection_comm":"커뮤니케이션 시리즈",
21    "product_collection_accessory":"액세서리",
22};
23
24// 登录,我的页面,购物车,logo   埋点标识切换
25const login_cart_sign_change = {
26    'LOG-IN' : 'login',
27    'MYPAGE' : 'my_page',
28    'CART' : 'shop_cart',
29    'logo' : 'logo',
30}
31
32function validateEmail(email) {
33    const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
34    return regex.test(email);
35}
36
37//view_item_list事件中通过分类名获取item_list_id ;  item_list_name为字符串,change_arr为数组
38function get_item_list_id(item_list_name,change_arr){
39    let list_id = '';
40    $.each(change_arr, function(index, value) {
41        if(value === item_list_name){
42            list_id = index;
43            return index;
44        }
45    });
46    return list_id;
47}
48
49function normalizeGa4TrackingProduct(row, fallbackCurrency){
50    if (!row) return null;
51    const currency = fallbackCurrency || 'KRW';
52    return {
53        item_id: row.item_id || '',
54        item_name: row.item_name || '',
55        item_variant: row.item_variant || null,
56        item_category: row.item_category || null,
57        item_category2: row.item_category2 || null,
58        item_category3: row.item_category3 || null,
59        item_brand: row.item_brand || 'SHOKZ',
60        discount: row.discount != null ? row.discount : 0,
61        price: row.price != null ? Number(row.price) : 0,
62        quantity: row.quantity != null ? row.quantity : 1,
63        currency: row.currency || currency,
64    };
65}
66
67function findGa4TrackingProductByItemId(itemId, fallbackCurrency){
68    const id = String(itemId || '');
69    if (!id) return null;
70
71    const initData = window.ga4InitData || [];
72    const byAttributeId = initData.find(function(it){
73        return String(it.pro_attribute_id || '') === id;
74    });
75    if (byAttributeId) {
76        return normalizeGa4TrackingProduct(byAttributeId, fallbackCurrency);
77    }
78
79    const byItId = initData.find(function(it){
80        return String(it.it_id || '') === id;
81    });
82    if (byItId) {
83        return normalizeGa4TrackingProduct(byItId, fallbackCurrency);
84    }
85
86    const skuMap = window.ga4_sku_product_map || {};
87    const itIdSkuMap = window.ga4_it_id_sku_map || {};
88
89    if (skuMap[id]) {
90        return normalizeGa4TrackingProduct(skuMap[id], fallbackCurrency);
91    }
92
93    const mappedSku = itIdSkuMap[id];
94    if (mappedSku && skuMap[mappedSku]) {
95        return normalizeGa4TrackingProduct(skuMap[mappedSku], fallbackCurrency);
96    }
97
98    return null;
99}
100
101/**
102 * 根据现价与原价计算折扣百分比,向最近的整数取整。
103 * @param {string|number} price 当前售价。
104 * @param {string|number} originalPrice 原始售价。
105 * @returns {string} 折扣百分比字符串,无折扣时返回 `'0'`。
106 */
107function get_discount(price, originalPrice) {
108    const p = parseFloat(price) || 0;
109    const op = parseFloat(originalPrice) || 0;
110    if (op === 0 || p >= op) {
111        return '0';
112    }
113    return Math.round(((op - p) / op) * 100).toString();
114}
115
116//获取url最后的一个路径段
117function getLastId(url){
118    if (typeof url !== 'string') return null; // 非字符串直接返回
119    const match = url.match(/\/([^/?]+)(?:\?|$)/);
120    const lastPathPart = match ? match[1] : null;
121    return lastPathPart
122}
123
124// 获取单个播放器信息的函数  这是谷歌播放器的数据
125function getPlayerInfo(player) {
126    if (!player) return null;
127    try {
128        const data = player.getVideoData();
129        const duration = player.getDuration();
130        const current = player.getCurrentTime();
131        const percent = ((current / duration) * 100).toFixed(1);
132        return {
133            "title": data.title,
134            "duration": duration,
135            "current": current,
136            "percent": percent + '%'
137        };
138    } catch (error) {
139        return null;
140    }
141}
142function set_player_interval(is_player,player,video_url,fail_count){
143    // 每1秒获取一次信息
144    let play1_interval = setInterval(() => {
145        const info = getPlayerInfo(player);
146        if(fail_count>=5){
147            clearInterval(play1_interval);
148        }
149        if(info.duration == 0){
150            fail_count++;
151        }else{
152            if(is_player === true){
153                clearInterval(play1_interval);
154                is_player = false;
155                return;
156            }
157            is_player = true;
158
159            ga4_listing_video_start(info.title,video_url,info.percent,info.current,info.duration);
160        }
161    }, 1000);
162}
163
164//判断元素是否在视图  由于不同的git已经迁移了,为了避免冲突,函数后面加了个new
165$.fn.isOnScreenNew = function() {
166    var element = this.get(0);
167    if(element){
168        var bounds = element.getBoundingClientRect();
169        return (
170            bounds.top <= window.innerHeight  &&
171            bounds.top >= 0  &&
172            bounds.left >= 0 &&
173            bounds.bottom >= 0 &&
174            bounds.bottom <= window.innerHeight &&
175            bounds.right >= 0
176        );
177    }
178};
179$.fn.isOnScreenBottomNew = function() {
180    var element = this.get(0);
181    if(element){
182        var bounds = element.getBoundingClientRect();
183        return (
184            bounds.bottom >= 0 &&
185            bounds.bottom <= window.innerHeight &&
186            bounds.right <= window.innerWidth
187        );
188    }
189};
190
191/**
192 * 可视范围内触发 通用函数
193 * @param {string|number}  selector 为id选择器
194 * @param {function} ga4_event 第二个参数为埋点内容函数。
195 * 比如banner停留一秒触发。
196 */
197function commonHandleViewportCheck(selector,ga4_event){
198    const selectorDiv = document.getElementById(selector);
199    let ga4_timer = null;
200    let isTriggered = false;
201    function handleViewportCheck() {
202        if ( $("#"+selector).isOnScreenNew() ) {
203            if (!ga4_timer && !isTriggered) {
204                ga4_timer = setTimeout(() => {
205                    selectorDiv.classList.add('active');
206                    ga4_event;
207                    isTriggered = true;
208                }, 1000);
209            }
210        } else {
211            if (ga4_timer) {
212                clearTimeout(ga4_timer);
213                ga4_timer = null;
214            }
215        }
216    }
217    // 添加滚动事件监听
218    window.addEventListener('scroll', handleViewportCheck);
219    // 初始检查
220    handleViewportCheck();
221}
222
223/**
224 * js模拟打开a链接新页面
225 * js 阻止a链接立即跳转,处理过后再调用此函数
226 * *
227 */
228function openInNewTab(url) {
229    // 1. 创建一个隐藏的 <a> 标签
230    const link = document.createElement('a');
231    // 2. 设置跳转地址
232    link.href = url;
233    // 3. 关键:设置 target 为 _blank,表示在新窗口/标签页打开
234    link.target = '_blank';
235    // 4. 可选:为了安全(防止新页面通过 window.opener 篡改原页面),建议加上 rel="noopener noreferrer"
236    link.rel = 'noopener noreferrer';
237    // 5. 将标签添加到 DOM 中(虽然不可见,但某些浏览器要求必须在 DOM 中才能触发)
238    document.body.appendChild(link);
239    // 6. 模拟点击事件
240    link.click();
241    // 7. 清理:移除标签
242    document.body.removeChild(link);
243}
244
245// 检查url参数是否存在
246function hasUrlParam(param) {
247    const urlParams = new URLSearchParams(window.location.search);
248    return urlParams.has(param);
249}
250
251
252function isMobile(){
253    try { return window.matchMedia && window.matchMedia('(max-width: 768px)').matches; } catch(e){ return false; }
254}
255
256
257function ga4_login_select_content(button_name){
258    window.dataLayer.push({ "event_parameters": null });
259    window.dataLayer.push({
260        "event":"ga4Event",
261        "event_name":"select_content",
262        "event_parameters":{
263            "content_type":"User Module",
264            "content_name":"Sign In",
265            "button_name":button_name
266        }
267    });
268}
269
270//旧版kakao登录按钮使用此事件
271function ga4_login_select_content_kakao_old(){
272    window.dataLayer.push({ "event_parameters": null });
273    window.dataLayer.push({
274        "event":"ga4Event",
275        "event_name":"select_content",
276        "event_parameters":{
277            "module_name":"User Module",
278            "content_name":"카카오 로그인",
279            "button_name":"카카오 로그인"
280        }
281    });
282}
283
284function ga4_login_login(){
285    window.dataLayer.push({ "event_parameters": null });
286    window.dataLayer.push({
287        "event":"ga4Event",
288        "event_name":"login",
289    });
290}
291
292function ga4_login_sign_up(){
293    window.dataLayer.push({ "event_parameters": null });
294    window.dataLayer.push({
295        "event":"ga4Event",
296        "event_name":"sign_up",
297    });
298}
299
300
301//登录,注册跨页面事件
302function ga4_login_domload_function(){
303    const login_source=localStorage.getItem('login_source')||'Official website';
304    if(g5_is_member){
305        if(localStorage.getItem('googleSignUpEventSent')){
306            ga4_login_sign_up();
307            localStorage.removeItem('googleSignUpEventSent');
308        }
309        if(!localStorage.getItem('googleLoginEventSent')){
310            ga4_login_login();
311            localStorage.setItem('googleLoginEventSent', g5_mb_no);
312        }
313    }
314    if(!g5_is_member&&localStorage.getItem('googleLoginEventSent')){
315        window.dataLayer.push({
316            "event":"ga4Event",
317            "event_name":"logout",
318            "event_parameters":{
319                "login_source":login_source
320            }
321        });
322        // console.log('[GTM EVENT] User is log out')
323        localStorage.removeItem('googleLoginEventSent')
324    }
325
326    //M端使用  PC端写在其他地方
327    $("#mb_login .sns-wrap").click(function(e){
328        if($(this).hasClass('login-a')){
329            ga4_login_select_content_kakao_old();
330        }else{
331            ga4_login_select_content("Kakao Signin");
332        }
333
334    })
335    $("#mb_login .ga4_btn_confirm").click(function(e){
336        const button = $(this).data('tracking-button-name');
337        ga4_login_select_content(button);
338
339    })
340}
341
342
343/**
344 * 提交订单 曝光事件 数据要存在浏览器缓存中,否则跳转页面就没有了  跨页面事件
345 * */
346function ga4_checkout_generateorder_event(status,error_msg = ''){
347    if(localStorage.getItem("ga4_checkout_form_commit")){
348        const ga4_checkout_coupon = localStorage.getItem("ga4_checkout_coupon");
349        const ga4_checkout_coupon_value = localStorage.getItem("ga4_checkout_coupon_value");
350        const ga4_checkout_payment_type = localStorage.getItem("ga4_checkout_payment_type");
351        const ga4_checkout_shipping = localStorage.getItem("ga4_checkout_shipping");
352        const ga4_checkout_point = localStorage.getItem("ga4_checkout_point");
353        const ga4_checkout_total_value = localStorage.getItem("ga4_checkout_total_value");
354        const ga4_checkout_product = JSON.parse(localStorage.getItem("ga4_checkout_product"));
355        const ga4_checkout_order_status = localStorage.getItem("ga4_checkout_order_status");
356        if(!status && !ga4_checkout_order_status){
357            return;
358        }else if(!status  && ga4_checkout_order_status){
359            status = ga4_checkout_order_status;
360        }
361
362        window.dataLayer.push({ "event_parameters": null });
363        window.dataLayer.push({
364            "event":"ga4Event",
365            "event_name":"order_button",
366            "event_parameters":{
367                "value":Number(ga4_checkout_total_value),
368                "coupon":ga4_checkout_coupon,                 //优惠卷名称
369                "coupon_value":ga4_checkout_coupon_value,            //优惠卷金额
370                "payment_type":ga4_checkout_payment_type,
371                "shipping":ga4_checkout_shipping,            //运费
372                "status":status,
373                "error_type":error_msg,
374                "point":ga4_checkout_point,            //   point:订单消耗的积分;
375                "item_list_id":"Checkout",
376                "item_list_name":"Checkout",
377                "items":  ga4_checkout_product
378            }
379        });
380
381        localStorage.removeItem('ga4_checkout_form_commit');
382        localStorage.removeItem('ga4_checkout_coupon');
383        localStorage.removeItem('ga4_checkout_coupon_value');
384        localStorage.removeItem('ga4_checkout_shipping');
385        localStorage.removeItem('ga4_checkout_point');
386        localStorage.removeItem('ga4_checkout_order_status');
387    }
388
389}
390
391function check_checkout_button_event(){
392    //针对异常情况,或者其他操作没有返回,在触发一次事件   下单事件
393    if(localStorage.getItem("ga4_checkout_form_commit") && localStorage.getItem("ga4_checkout_order_status")){
394        setTimeout(() => {
395            ga4_checkout_generateorder_event("",'');
396        }, 1000);
397
398    }
399}
400
401//所有的跨页面事件,统一写在common里面
402document.addEventListener('DOMContentLoaded', () => {
403    //里面有跨页面事件  登录成功和注册成功后事件  以及下订单事件
404    ga4_login_domload_function();
405    check_checkout_button_event();
406});
407
408
409
410
411
412
413
414

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.