前情提要:
學習 muffet 的筆記整理


如何使用 local preview

我們會在 local 開發的時候,會需要把頁面上的所 HOST 的那張 JavaScript 導入到 local 端。 用 ModHeader 這個小工具是因為,為了導到 local 開發,我們 local 可以起一個簡單的 server,來去 Host 現在這一家客戶的 muffet 檔案。

一般來說,我們在 muffet 的 repo 中,當我們發一個 pr merge 到 main 分支,並且跑完 CI/CD 後,會 compile 成一個 json 檔案,我們會 Host 在這個地方,這個東西我們放在 GCP 上面(在 cloud storage 裡面)。


當我在本地開發時,會需要把 https://ecs.tagtoo.co/js/1165.js 導入到我們的本地,他會偵測 chrome browser 所發出的 HTTPrequest 中的 header


ModHeader設定

開啟 ModHeader chrome 插件,並點擊 + 選項,接著點擊 Redirect URL 選項,這時候會出現兩個 field,左邊的欄位輸入 https://ecs.tagtoo.co/js/1165.js,右邊的欄位輸入 http://localhost:3000/1165.js,再來進入這個文件 muffet repo

Ps. 一開始 ModHeader 設定好,去 dev-tool 中的 network,會看到 307 Internal Redirect,他會去找 local 下 1165 的 js 檔案

muffet 檔案設定

照著教學,先 clone 下來 muffet 的檔案,接著把進入資料夾後, 輸入 npm install 把一些插件下載下來後,就可以輸入以下指令, npm run preview 1665,再來就可以打開客戶的網站、打開 dev tool,進入到 console 的分頁,就可以開始開發了!

Ps. 上面輸入的連結都是實際案例,實際上要根據客戶的 ec ID 來改變連結輸入,以上面的狀況來說 1165 就是不同客戶都會有不同的數字
Ps. 記得一定要用 npm ,用 yarn 會噴錯誤

學習日誌

只有 ec ID

程式碼:

const config = new Config({
  id: 1165,
})

console:

> fire all init (ab7b7cbc-680e-4702-a1de-a5e8c44a93a4)
> {
>     "tagtoo": {
>         "currency": "TWD"
>     },
>     "_eventId": "ab7b7cbc-680e-4702-a1de-a5e8c44a93a4"
> }
> complete initializing tracker
> fire tagtoo init event

加上 google

程式碼:

const config = new Config({
  id: 1165,
  google: {
    trackIds: [['UA-34980571-1'], ['AW-781127587', { allow_enhanced_conversions: true }], ['G-F3CYVHGRHQ']],
  },  
})

console:

加上google後,看console會發現,物件裡面會多了google的追蹤碼,並且log會說gtag都已經初始化完成,並把數據送出

fire all init (410258d0-b555-4792-a054-7cbcf773c759)

> {
>     "google": {
>         "currency": "TWD",
>         "trackIds": [
>             [
>                 "UA-34980571-1"
>             ],
>             [
>                 "AW-781127587",
>                 {
>                     "allow_enhanced_conversions": true
>                 }
>             ],
>             [
>                 "G-F3CYVHGRHQ"
>             ]
>         ]
>     },
>     "tagtoo": {
>         "currency": "TWD"
>     },
>     "_eventId": "410258d0-b555-4792-a054-7cbcf773c759"
> }

complete initializing tracker

> gtag has been installed: true
> send: gtag('config', "UA-34980571-1", {"groups":"tagtoo_ua"})
> gtag has been installed: true
> send: gtag('config', "AW-781127587", {"allow_enhanced_conversions":true,"groups":"tagtoo_aw"})
> gtag has been installed: true
> send: gtag('config', "G-F3CYVHGRHQ", {"groups":"tagtoo_g"})
> fire google init event

fire tagtoo init event

加上 facebook

程式碼:

const config = new Config({
  id: 1165,
  google: {
    trackIds: [['UA-34980571-1'], ['AW-781127587', { allow_enhanced_conversions: true }], ['G-F3CYVHGRHQ']],
  },  
  facebook: {
    contentType: 'product',
    trackIds: [['344159426146914', '2277901115614919']],
  },  
})

console:

把facebook追蹤碼加上去後,跟google追蹤碼差不多,會把facebook的追蹤碼加進物件,並說facebook已經初始化

> {
>     "facebook": {
>         "currency": "TWD",
>         "contentType": "product",
>         "trackIds": [
>             [
>                 "344159426146914",
>                 "2277901115614919"
>             ]
>         ]
>     },
>     "google": {
>         "currency": "TWD",
>         "trackIds": [
>             [
>                 "UA-34980571-1"
>             ],
>             [
>                 "AW-781127587",
>                 {
>                     "allow_enhanced_conversions": true
>                 }
>             ],
>             [
>                 "G-F3CYVHGRHQ"
>             ]
>         ]
>     },
>     "tagtoo": {
>         "currency": "TWD"
>     },
>     "_eventId": "0d8df3ef-23fd-4324-80d7-c5c1f7509404"
> }
complete initializing tracker

> fire facebook init event

gtag has been installed: true
send: gtag('config', "UA-34980571-1", {"groups":"tagtoo_ua"})
gtag has been installed: true
send: gtag('config', "AW-781127587", {"allow_enhanced_conversions":true,"groups":"tagtoo_aw"})
gtag has been installed: true
send: gtag('config', "G-F3CYVHGRHQ", {"groups":"tagtoo_g"})
fire google init event
fire tagtoo init event

加上 unitrack

程式碼:

const config = new Config({
  id: 1165,
  google: {
    trackIds: [['UA-34980571-1'], ['AW-781127587', { allow_enhanced_conversions: true }], ['G-F3CYVHGRHQ']],
  },  
  facebook: {
    contentType: 'product',
    trackIds: [['344159426146914', '2277901115614919']],
  },  
  unitrack: {
    token: '510708f9f634bf6a419e522cb346a1b0510119c93dbf338edc8cbab2e266',
    scopes: ['tagtoo'],
  },  
})

console:

fire all init (10c51ff8-09c3-4416-9b95-9211dcd5bf0e)

> {
>     "facebook": {
>         "currency": "TWD",
>         "contentType": "product",
>         "trackIds": [
>             [
>                 "344159426146914",
>                 "2277901115614919"
>             ]
>         ]
>     },
>     "google": {
>         "currency": "TWD",
>         "trackIds": [
>             [
>                 "UA-34980571-1"
>             ],
>             [
>                 "AW-781127587",
>                 {
>                     "allow_enhanced_conversions": true
>                 }
>             ],
>             [
>                 "G-F3CYVHGRHQ"
>             ]
>         ]
>     },
>     "tagtoo": {
>         "currency": "TWD"
>     },
>     "unitrack": {
>         "currency": "TWD",
>         "token": "510708f9f634bf6a419e522cb346a1b0510119c93dbf338edc8cbab2e266",
>         "scopes": [
>             "tagtoo"
>         ]
>     },
>     "_eventId": "10c51ff8-09c3-4416-9b95-9211dcd5bf0e"
> }

complete initializing tracker
fire facebook init event
gtag has been installed: true
send: gtag('config', "UA-34980571-1", {"groups":"tagtoo_ua"})
gtag has been installed: true
send: gtag('config', "AW-781127587", {"allow_enhanced_conversions":true,"groups":"tagtoo_aw"})
gtag has been installed: true
send: gtag('config', "G-F3CYVHGRHQ", {"groups":"tagtoo_g"})
fire google init event
fire tagtoo init event

> fire unitrack init event

加上 google-AdWords 的個別轉換

程式碼:

const config = new Config({
  id: 1165,
  google: {
    trackIds: [['UA-34980571-1'], ['AW-781127587', { allow_enhanced_conversions: true }], ['G-F3CYVHGRHQ']],
  },
  facebook: {
    contentType: 'product',
    trackIds: [['344159426146914', '2277901115614919']],
  },
  unitrack: {
    token: '510708f9f634bf6a419e522cb346a1b0510119c93dbf338edc8cbab2e266',
    scopes: ['tagtoo'],
  },
  conversions: [
    {
      action: 'purchase',
      google: 'AW-781127587/hVxZCPGMnowBEKOfvPQC',
    },
    {
      action: 'addToCart',
      google: 'AW-781127587/BwSVCL_IsOIBEKOfvPQC',
    },
    {
      action: 'register',
      google: 'AW-781127587/NU_TCMvUu-IBEKOfvPQC',
    },
    {
      action: 'pageView',
      google: 'AW-781127587/srOTCM7Uu-IBEKOfvPQC',
    },
  ],
})

console:

因為google-AdWords除了追蹤碼,還有像是購買、加入購物車等其他的追蹤設定,因此要額外設定就要加在這邊

fire all init (53aed957-2ac1-47fa-a975-e042de266b34)

> {
>     ...省略
>     "conversions": [
>         {
>             "action": "purchase",
>             "google": "AW-781127587/hVxZCPGMnowBEKOfvPQC"
>         },
>         {
>             "action": "addToCart",
>             "google": "AW-781127587/BwSVCL_IsOIBEKOfvPQC"
>         },
>         {
>             "action": "register",
>             "google": "AW-781127587/NU_TCMvUu-IBEKOfvPQC"
>         },
>         {
>             "action": "pageView",
>             "google": "AW-781127587/srOTCM7Uu-IBEKOfvPQC"
>         }
>     ],
>     "_eventId": "53aed957-2ac1-47fa-a975-e042de266b34"
> }

> register conversions: purchase
> register conversions: addToCart
> register conversions: register
> register conversions: pageView

complete initializing tracker
fire facebook init event
gtag has been installed: true
send: gtag('config', "UA-34980571-1", {"groups":"tagtoo_ua"})
gtag has been installed: true
send: gtag('config', "AW-781127587", {"allow_enhanced_conversions":true,"groups":"tagtoo_aw"})
gtag has been installed: true
send: gtag('config', "G-F3CYVHGRHQ", {"groups":"tagtoo_g"})
fire google init event
fire tagtoo init event
fire unitrack init event

trackProductPage

tracker 如何使用

  1. 幫事件取名
  2. 觸發事件的頁面連結
  3. 抓取產品的屬性
  4. 把抓到的產品設定成一個物件
  5. tracker 把產品資訊當作特定事件傳出去

觸發的 code

// 到達產品頁面時,抓取產品的內容
config.events = new Event({
  // 幫事件取名 - 追蹤產品頁
  name: 'trackProductPage',
  // 到哪個頁面連結會觸發此事件 - 用contain確認是否有包含此連結
  trigger: pageView.path.contains('/SalePage/Index'),
  delay: 1000,
  // 開始抓取
  fn: async () => {
    // 產品瑩稱
    const title = document.querySelector('.salepage-title').textContent.trim()
    // 圖片連結
    const imageUrl = document.querySelector('.large-image')?.src
    // 產品描述
    const description = document.querySelector('.salepage-feature').textContent.replace(/\s/g, '')
    // 產品價格
    const price = document.querySelector('.salepage-price').textContent.replace(/[^0-9.]/g, '')
    // 商品價格、如果沒抓到這個,就是price價格
    const storePrice =
      document.querySelector('.salepage-suggestprice')?.textContent.replace(/[^0-9.]/g, '') || price
    // 麵包蟹
    const categoryPath = [...document.querySelectorAll('.breadcrumb a')]
      .map((e) => e.textContent.trim())
      .join(' > ')
    // 產品 ID
    const productId = getProductIdByUrl(location.href)
    // 
    const sku = `${productId}`
    // 活動
    const customLabel1 = [...document.querySelectorAll('.salepage-promotion-title')].pop()?.textContent
    // 產品所有資訊
    const productInfo = {
      title: title,
      sku: sku,
      price: Number(price),
      storePrice: Number(storePrice),
      categoryPath: categoryPath,
      description: description,
      imageUrl: imageUrl,
      customLabel1: customLabel1,
    }
    logger('productInfo', productInfo)
    logger('------------')
    // 新增產品物件
    const product = new Product(productInfo)
    // 新增商店的產品細節 - 並且把product id 補上,這邊取名叫做sku
    const storeProduct = new Product({ ...productInfo, sku: `${ecName}:product:${productId}` })



    // tracker 把 product 該產品送出,viewItem 這個事件
    tracker('viewItem', product)

  },
})

tracker 會印出哪些 console

因為剛剛在最後使用 tracker 的原因,我們來看看 console 會印出哪些東西

> fire all viewItem (e510d696-50b7-4682-8125-d85fef8aea5b)
>   Product(12) {'advertiserId' => 1165, 'title' => '江戶勝|男裝|雷射印花襯衫', 'sku' => '8852710', 'link' => 'https://www.edwin.com.tw/SalePage/Index/8852710', 
>   'categoryPath' => '首頁 > 男裝上衣 > 襯衫/POLO衫', …}
> fire facebook viewItem event
> send gtag event 'view_item' to ["AW-781127587"]
>   {items: Array(1), value: 2690, currency: 'TWD', send_to: Array(1)}
> send gtag event 'view_item' to ["G-F3CYVHGRHQ"]
>   {items: Array(1), value: 2690, currency: 'TWD', send_to: Array(1)}
> send gtag event 'view_item' to ["UA-34980571-1"]
>   {items: Array(1), value: 2690, currency: 'TWD', event_category: 'ecommerce', event_label: '8852710', …}
> fire google viewItem event
> tagtoo.tracker.event(): ViewContent, [object Object]
> fire tagtoo viewItem event
> fire unitrack viewItem event  

Item物件裡面是這樣的

{
    "items": [
        {
            "id": "8852710",
            "name": "江戶勝|男裝|雷射印花襯衫",
            "item_id": "8852710",
            "item_name": "江戶勝|男裝|雷射印花襯衫",
            "price": 2690,
            "quantity": 1
        }
    ],
    "value": 2690,
    "currency": "TWD",
    "send_to": [
        "AW-781127587"
    ]
}

可以發現用 tracker 把 product 以 viewItem 事件送出去後,會發生這幾件事情

  1. 觸發所有 viewItem 事件
  2. 觸發 FB 的 viewItem 事件
  3. 觸發 google 的 viewItem 事件
  4. 把 viewItem 用 gtag 分別送到有串接的 google 渠道
  5. 觸發 tagtoo 的 viewItem 事件
  6. 觸發 unitrack 的 viewItem 事件

addToCart 事件

下面會用 forEach 的原因是因為這個範例網站有兩個按鈕以上,所以就抓指定的按鈕就好

// 先抓到所有按鈕
const addToCartButtons = document.querySelectorAll('.add-to-cart-btn')

// 按鈕有這種屬性
addToCartButtons.forEach((ele) => {
    ele._is_Bind = true
})

// 抓到這個屬性後,當點下此按鈕就送出這個事件
addToCartButtons.forEach((button) => {
    button.addEventListener('click', () => {
    tracker('addToCart', product)
    })
})    

印出的 console

> fire all addToCart (929ad43f-65e1-4ae2-90aa-ec333b6f8a11)
>   Product(12) {'advertiserId' => 1165, 'title' => '江戶勝|男裝|雷射印花襯衫', 'sku' => '8852710', 'link' => 'https://www.edwin.com.tw/SalePage/Index/8852710', 
>   'categoryPath' => '首頁 > 男裝上衣 > 襯衫/POLO衫', …}
> fire facebook addToCart event
> send gtag event 'add_to_cart' to ["AW-781127587"]
>   {items: Array(1), value: 2690, currency: 'TWD', send_to: Array(1)}
> send gtag event 'add_to_cart' to ["G-F3CYVHGRHQ"]
>   {items: Array(1), value: 2690, currency: 'TWD', send_to: Array(1)}
> send gtag event 'add_to_cart' to ["UA-34980571-1"]
>   {items: Array(1), value: 2690, currency: 'TWD', event_category: 'ecommerce', event_label: '8852710', …}
> fire google addToCart event
> tagtoo.tracker.event(): AddToCart, [object Object]
> fire tagtoo addToCart event
> fire unitrack addToCart event
> fire facebook addToCart conversion
> send gtag event 'conversion' to "AW-781127587/BwSVCL_IsOIBEKOfvPQC"
>   {send_to: 'AW-781127587/BwSVCL_IsOIBEKOfvPQC', value: 2690, currency: 'TWD', transaction_id: '8c214cd1-cfea-46ef-8ed1-7e4a31ce46e2'}
> fire google addToCart conversion
> fire tagtoo addToCart conversion
> fire unitrack addToCart conversion

用 tracker 傳的東西,大致跟剛剛的 viewItem 事件很像,不過這邊還有多幾個渠道,像是 GADs的轉換 ,就是前面 viewItem 沒有的

addToWishlist 事件

// 抓到愛心,就可以傳送addToWishlist
const addToWishListButtons = document.querySelectorAll('.fa-heart-o')
addToWishListButtons.forEach((button) => {
    button.addEventListener('click', () => {
    tracker('addToWishlist', product)
    })
})

console

fire all addToWishlist (f0a30cea-b530-4ff2-85b6-c1374358aa0b)
  Product(12) {'advertiserId' => 1165, 'title' => '江戶勝|男裝|雷射印花襯衫', 'sku' => '8852710', 'link' => 'https://www.edwin.com.tw/SalePage/Index/8852710', 
  'categoryPath' => '首頁 > 男裝上衣 > 襯衫/POLO衫', …}
fire facebook addToWishlist event
send gtag event 'add_to_wishlist' to ["AW-781127587"]
{items: Array(1), value: 2690, currency: 'TWD', send_to: Array(1)}
send gtag event 'add_to_wishlist' to ["G-F3CYVHGRHQ"]
{items: Array(1), value: 2690, currency: 'TWD', send_to: Array(1)}
send gtag event 'add_to_wishlist' to ["UA-34980571-1"]
{items: Array(1), value: 2690, currency: 'TWD', event_category: 'ecommerce', event_label: '8852710', …}
fire google addToWishlist event
fire unitrack addToWishlist event

總結

在產品頁這邊,總共送了三個tracker,只要先抓好好產品資訊,再使用 tracker 就好

> event:
> name: 'trackProductPage'
> 
> tracker:
> 1、 tracker('viewItem', product)
> 2、 tracker('addToCart', product)
> 3、 tracker('addToWishlist', product)

trackCartPage

觸發的 code

config.events = new Event({
  // trackCartPage事件
  name: 'trackCartPage',
  // 觸發頁面 - 要登入來有這頁面
  trigger: pageView.path.contains('/ShoppingCart/'),
  // 要用非同步抓取,因為頁面會新增產品到購物車,他會重新loading
  fn: async () => {
    // 宣告一個 cart 物件,這邊 Cart 是已經定義好的物件,只有宣告一個新變數繼承他就好
    const cart = new Cart()    
    logger("cart before before---", cart)
    await waitForSelector('.merchandise').then(() => {
      // 抓到產品細節
      const itemElements = document.querySelectorAll('.merchandise')
      const products = [...itemElements].map((e) => {
        const sku = e.querySelector('.cart-content a').getAttribute('data-product-id')
        const title = e.querySelector('.merchandise-title')?.textContent.trim()
        const price = e.querySelector('.fee')?.textContent.replace(/[^0-9.]/g, '')
        const quantity = e.querySelector('.qty')?.textContent
        // 用一個新物件包住他們
        const productInfo = {
          title: title,
          sku: sku,
          price: Number(price),
          qty: Number(quantity),
        }
        return productInfo
      })
      
      // 當今天有買一個產品以上,會是一個陣列,因此我們需要用forEach把各個產品加近
      products.forEach((productInfo) => {
        cart.add(new Product(productInfo))
      })      
      // 這個是保存狀態用
      cart.save()
      tracker('trackCart', cart)      
      tracker('viewCart', cart)      
      tracker('checkout', cart)      
    })

    window.addEventListener('hashchange', (e) => {
      if (/#\/Info\/Flow/.test(e.newURL)) {
        document.querySelector('button.next-step-btn').addEventListener('click', () => {
          const payment = document.querySelector('.payment-list .icon-radio-selected')?.nextElementSibling
            .textContent
          const shipping = document.querySelector('.active span[data-qe-id="delivery_method_name"]')?.textContent
          if (payment && shipping) {
            cart.payment = payment_map[payment]
            cart.shippingMethod = shipping_map[shipping]            
            tracker('addShippingInfo', cart)            
            tracker('addPaymentInfo', cart)
          }
        })
      }
    })
  },
})

Cart() 物件介紹

我們用一個變數繼承 Cart 物件,來看一下原本啥都還沒加入前,裡面有哪些東西

先宣告後,用 logger 印出來

const cart = new Cart()
logger("cart before before---", cart)

看看 console :

> cart before before--- Cart {products: Array(0), total: 0, tax: 0, shipping: 0, currency: undefined, …}

cart.save()

我們把購物車裡面的產品都加入 cart 後,來看一下後面來有一行 cart.save,這個是幹嘛用的呢?
他的用意主要是把數據存在瀏覽器中,這樣下次就進入頁面可以直接載入購物車的資料

tracker(‘trackCart’, cart)

跟前面事件差不多,會觸發一些事件、google、facebook、的 view_cart 事件

> fire all trackCart (6556082e-f437-4391-9d10-4b3eb26f6d3e)
> Cart {products: Array(3), total: 2475, tax: 0, shipping: 0, currency: undefined, …}
> fire facebook trackCart event
> send gtag event 'view_cart' to ["AW-781127587"]
> {transaction_id: undefined, value: 2475, shipping: 0, currency: 'TWD', coupon: undefined, …}
> send gtag event 'view_cart' to ["G-F3CYVHGRHQ"]
> {transaction_id: undefined, value: 2475, shipping: 0, currency: 'TWD', coupon: undefined, …}
> send gtag event 'view_cart' to ["UA-34980571-1"]
> {transaction_id: undefined, value: 2475, shipping: 0, currency: 'TWD', coupon: undefined, …}
> fire google trackCart event
> tagtoo.tracker.event(): AddToCart, [object Object]
> fire tagtoo trackCart event
> fire unitrack trackCart event

tracker(‘viewCart’, cart)

> fire all viewCart (55872057-86b9-4738-b9df-6a6225f9f159)
> Cart {products: Array(3), total: 2475, tax: 0, shipping: 0, currency: undefined, …}
> send gtag event 'view_cart' to ["AW-781127587"]
> {transaction_id: undefined, value: 2475, shipping: 0, currency: 'TWD', coupon: undefined, …}
> send gtag event 'view_cart' to ["G-F3CYVHGRHQ"]
> {transaction_id: undefined, value: 2475, shipping: 0, currency: 'TWD', coupon: undefined, …}
> send gtag event 'view_cart' to ["UA-34980571-1"]
> {transaction_id: undefined, value: 2475, shipping: 0, currency: 'TWD', coupon: undefined, …}
> fire google viewCart event
> fire unitrack viewCart event

tracker(‘checkout’, cart)

> fire all checkout (fea24f3a-171d-40c7-bde3-137c3096cff1)
> Cart {products: Array(3), total: 2475, tax: 0, shipping: 0, currency: undefined, …}
> fire facebook checkout event
> send gtag event 'begin_checkout' to ["AW-781127587"]
> {transaction_id: undefined, value: 2475, shipping: 0, currency: 'TWD', coupon: undefined, …}
> send gtag event 'begin_checkout' to ["G-F3CYVHGRHQ"]
> {transaction_id: undefined, value: 2475, shipping: 0, currency: 'TWD', coupon: undefined, …}
> send gtag event 'begin_checkout' to ["UA-34980571-1"]
> {transaction_id: undefined, value: 2475, shipping: 0, currency: 'TWD', coupon: undefined, …}
> fire google checkout event
> fire unitrack checkout event

tracker(‘addShippingInfo’, cart)、tracker(‘addPaymentInfo’, cart)

這兩個 tracker 都因為點下按鈕後,都會直接轉跳畫面,tracker 的 console 會無法抓到,所以這邊就先不抓了。

不過我們可以來看一下他的JS抓法:

window.addEventListener('hashchange', (e) => {
  // 確認連結改變時,連結有沒有符合這個規則,.test 就是判斷前片的規則有沒有符合正規表達式
  if (/#\/Info\/Flow/.test(e.newURL)) {
    
    // 這邊會抓到第二層的購物車 - 第二層是在新增物流、金流,點下去後,會觸發下面兩個 tracker
    document.querySelector('button.next-step-btn').addEventListener('click', () => {          
      const payment = document.querySelector('.payment-list .icon-radio-selected')?.nextElementSibling
        .textContent
      const shipping = document.querySelector('.active span[data-qe-id="delivery_method_name"]')?.textContent
      if (payment && shipping) {
        cart.payment = payment_map[payment]
        cart.shippingMethod = shipping_map[shipping]

        // 點下這兩個連結後,畫面會直接轉跳,所以這兩個tracker會無法抓到他們的 console        
        tracker('addShippingInfo', cart)        
        tracker('addPaymentInfo', cart)
      }
    })
  }
})

trackTransactionPage

觸發的 code

config.events = new Event({
  name: 'trackTransactionPage',
  trigger: pageView.path.contains('/V2/Pay/Finish'),
  delay: 1500,
  fn: () => {
    const storageKeyName = '_sent_trans_ids'
    const storeIds = storage.get(storageKeyName, [])

    const getOrderId = () => {
      const orderCodeFromJs = window.nineyi?.ServerData?.payProcessData?.TradesOrderGroup?.TradesOrderGroup_Code
      if (orderCodeFromJs) {
        return orderCodeFromJs
      }
      const orderCodeFromElement = document
        .querySelector('[data-qe-id="tradesOrderGroup_Code"]')
        .textContent.trim()
      if (orderCodeFromElement) {
        return orderCodeFromElement
      }
      return new Date().toISOString().replace(/\D/g, '')
    }

    const orderId = getOrderId()

    if (storeIds.includes(orderId)) {
      logger('same order ID !')
      return
    }

    const cart = Cart.load()
    cart.orderId = orderId

    // enhanced conversions
    const userInfo = getUserInfoAtFinishedPage()
    window.gtag('set', 'user_data', {
      email: userInfo.email,
      phone_number: userInfo.phone,
    })
  
    tracker('purchase', cart)
    const skus = cart.products.map((product) => product.get('sku')).join(', ')
    sendUETCustomEvent('ecommerce', 'Purchase', skus, cart.total)
    Cart.clear()
    storeIds.push(orderId)
    storage.set(storageKeyName, storeIds, 60 * 60 * 30)
  },
})

Cart.load()

我們在這邊直接載入前面在購物車裡塞入的東西,把它印出來看看

> 下面這個物件裡面,就是前面塞的產品
> Cart {products: Array(1), total: 1290, tax: 0, shipping: 0, currency: undefined, …}

tracker(‘purchase’, cart)

跟前面差不多,會把裝在 cart 裡面的產品,用 purchase 事件送出去

> fire all purchase (650c84ed-0dad-4ddd-b597-244d5dee225c)
> Cart {products: Array(1), total: 1290, tax: 0, shipping: 0, currency: undefined, …}
> fire facebook purchase event
> send gtag event 'purchase' to ["AW-781127587"]
> {transaction_id: 'TG230526SA00KT', value: 1290, shipping: 0, currency: 'TWD', coupon: undefined, …}
> send gtag event 'purchase' to ["G-F3CYVHGRHQ"]
> {transaction_id: 'TG230526SA00KT', value: 1290, shipping: 0, currency: 'TWD', coupon: undefined, …}
> send gtag event 'purchase' to ["UA-34980571-1"]
> {transaction_id: 'TG230526SA00KT', value: 1290, shipping: 0, currency: 'TWD', coupon: undefined, …}
> fire google purchase event
> tagtoo.tracker.event(): Purchase, [object Object]
> fire tagtoo purchase event
> fire unitrack purchase event
> fire facebook purchase conversion
> send gtag event 'conversion' to "AW-781127587/hVxZCPGMnowBEKOfvPQC"
> {send_to: 'AW-781127587/hVxZCPGMnowBEKOfvPQC', value: 1290, currency: 'TWD', transaction_id: 'TG230526SA00KT'}
> fire google purchase conversion
> fire tagtoo purchase conversion
> fire unitrack purchase conversion

storage ID 的設定

第一次完成訂單 - 會存取該筆訂單 ID

// 抓到 store id
const storageKeyName = '_sent_trans_ids'
// 這個 storage 是 import 進來的,使用 get 方法拿到有 storageKeyName 該字串的字串
const storeIds = storage.get(storageKeyName, [])

// 用fc抓到order ID
const getOrderId = () => {
  // 這邊一連串,是頁面會有一大段的字串,這一大段裡面,會有之前訂單的 ID,把他抓出來就好
  const orderCodeFromJs = window.nineyi?.ServerData?.payProcessData?.TradesOrderGroup?.TradesOrderGroup_Code
  if (orderCodeFromJs) {
    return orderCodeFromJs
  }
  // 這一段也是在抓取訂單的 id
  const orderCodeFromElement = document
    .querySelector('[data-qe-id="tradesOrderGroup_Code"]')
    .textContent.trim()
  if (orderCodeFromElement) {
    return orderCodeFromElement
  }
  return new Date().toISOString().replace(/\D/g, '')
}

getOrderId function

這一個function有一大段看不懂的字串,是來源於這裡

> <script>
>   window.nineyi.ServerData = window.nineyi.ServerData || {};
>   window.nineyi.ServerData = {
>       appDownloadLink: '/ref/41309/shophome/41309',
>      ....省略}
> </script>

判斷是否為第一次產生訂單ID,如果是第一次,就不會符合下面的if判斷式:


// 給定一個變數
const orderId = getOrderId()

// 因爲我們在一筆訂單完成後,會儲存該筆訂單ID到storeIds裡面,因此就不會重複送tracker過去
if (storeIds.includes(orderId)) {
  logger('same order ID !')
  return
}

// 載入我們前面塞進 Cart 裡面的產品
const cart = Cart.load()    
cart.orderId = orderId

加強型電子商務:

// enhanced conversions
const userInfo = getUserInfoAtFinishedPage()
window.gtag('set', 'user_data', {
  email: userInfo.email,
  phone_number: userInfo.phone,
})

第一次產生 orderID 的話,我們把 ID 推進 storeID 中,並存進 storage 裡面:

storeIds.push(orderId)
storage.set(storageKeyName, storeIds, 60 * 60 * 30)

第二次進到完成頁面 - 會讀取該筆訂單 ID

第二次就會因為 orderID 已經產生,所以就 if 判斷式就會成立

// 給定一個變數
const orderId = getOrderId()

// 因爲我們在一筆訂單完成後,會儲存該筆訂單ID到storeIds裡面,因此就不會重複送tracker過去
if (storeIds.includes(orderId)) {
  logger('same order ID !')
  return
}

trackRegister

config.events = new Event({
  name: 'trackRegister',
  trigger: pageView.path.contains('Login/Index'),
  delay: 1500,
  fn: () => {
    document.querySelector('.button__Button-sc-fjm4ds-0').addEventListener('click', () => {
      const phone = document.querySelector('input[name="cellPhone"]').value
      if (phone) {
        tracker('register', 'member')
        tracker('unitrack', 'add', { phone })
      }
    })

    document.querySelector('.FBLoginButton-sc-7622l3').addEventListener('click', () => {
      tracker('register', 'facebook')
    })

    document.querySelector('.LINELoginButton-sc-1r0yu6d').addEventListener('click', () => {
      tracker('register', 'line')
    })
  },
})

console 印出來的樣子:

> fire all register (f5fce53f-f124-45f1-ab79-9b7c00142a10)
> member
> fire facebook register event
> send gtag event 'sign_up' to ["AW-781127587"]
> {method: 'member', send_to: Array(1), currency: 'TWD'}
> send gtag event 'sign_up' to ["G-F3CYVHGRHQ"]
> {method: 'member', send_to: Array(1), currency: 'TWD'}
> send gtag event 'sign_up' to ["UA-34980571-1"]
> {event_category: 'engagement', event_label: 'member', send_to: Array(1), currency: 'TWD'}
> fire google register event
> fire unitrack register event
> fire facebook register conversion
> send gtag event 'conversion' to "AW-781127587/NU_TCMvUu-IBEKOfvPQC"
> {send_to: 'AW-781127587/NU_TCMvUu-IBEKOfvPQC', value: undefined, currency: 'TWD', transaction_id: '36591fd9-481b-4fe2-9557-ba629977a387'}
> fire google register conversion
> fire tagtoo register conversion
> fire unitrack register conversion
> fire unitrack add (0beb0315-e84a-4249-99f3-cb64220fd27b)
> {phone: '910158315'}

trackLogin

再來是輸入密碼的地方,首先我們要確認網站連結

確認網站連結

選擇登入方式的網站連結

https://www.edwin.com.tw/V2/Login/Index/?rt=https%3A%2F%2Fwww.edwin.com.tw%2F&unLoginId=2b04691f-c967-47d8-941c-a746c152750c&reason=notlogin&officialShopId=41309&authRedirectType=Default#/

輸入密碼的網站連結

https://www.edwin.com.tw/V2/Login/Index/?rt=https%3A%2F%2Fwww.edwin.com.tw%2F&unLoginId=2b04691f-c967-47d8-941c-a746c152750c&reason=notlogin&officialShopId=41309&authRedirectType=Default#/login

觸發的code

會發現輸入密碼的網站連結,最後有 login,因此trigger要改成下面那樣

config.events = new Event({
  name: 'trackLogin',
  trigger: pageView.path.contains('/Login/Index').and(hashChange.hash.contains('login')),
  delay: 1500,
  fn: () => {
    // 確認密碼有填寫後,就可以送 login 事件過去了
    document.querySelector('.button__Button-sc-fjm4ds-0').addEventListener('click', () => {
      const pwd = document.querySelector('input[name="password"]').value
      if (pwd) {
        tracker('login', 'member')
      }
    })

    document.querySelector('.FBLoginButton-sc-7622l3').addEventListener('click', () => {
      tracker('register', 'facebook')
    })

    document.querySelector('.LINELoginButton-sc-1r0yu6d').addEventListener('click', () => {
      tracker('register', 'line')
    })
  },
})

trackIsMember

  1. tracker - lead

觸發的 code

config.events = new Event({
  // 是不是會員
  name: 'trackIsMember',
  trigger: pageView.all,
  delay: 2000,
  fn: () => {
    // 直接抓會員登出按鈕,如果有按鈕,代表有登入
    const logoutElement = document.querySelector('a[href="/member/logout-p.php"]')
    if (logoutElement) {
      // 送出 lead 事件,並標記是 isMember
      tracker('lead', { label: 'isMember' })
    }
  },
})

trackSearch

  1. tracker - search

觸發的 code

config.events = new Event({
  name: 'trackSearch',

  // 這是學長寫的
  // trigger: pageView.path.contains('/searchall-products').and(pageView.query('keywords').exists()),

  // 這是我後來改的
  trigger: pageView.path.contains('/Search'),
  delay: 1500,
  fn: () => {
    // 這一句 = 下面註解掉的三句
    const searchKey = new URL(location.href).searchParams.get('q')
    // const queryString = new URL(location.href).search
    // const searchParams = new URLSearchParams(queryString);
    // const keyword = searchParams.get("q");
    
    tracker('search', searchKey)    
  },  
})

console印出來的樣子:

> fire all search (7fea4441-4a3c-4e67-aa23-08508652abfc)
> 帽子
> fire facebook search event
> send gtag event 'search' to ["AW-781127587"]
> {search_term: '帽子', send_to: Array(1), currency: 'TWD'}
> send gtag event 'search' to ["G-F3CYVHGRHQ"]
> {search_term: '帽子', send_to: Array(1), currency: 'TWD'}
> send gtag event 'search' to ["UA-34980571-1"]
> {event_category: 'engagement', event_label: '帽子', send_to: Array(1), currency: 'TWD'}
> fire google search event
> fire unitrack search event

trackViewItemList

  1. 註解的程式碼,都是之前人寫的
  2. tracker - viewItemList

觸發的 code

// 分類事件
config.events = new Event({
  name: 'trackViewItemList',
  // 網址有包含以下的關鍵字
  trigger: pageView.path.contains('/SalePageCategory/'),
  delay: 1500,
  fn: () => {
    // 麵包屑
    // const breadcrumb = [...document.querySelectorAll('.sc-LzLsx li')]
    //   .map(($li) => $li.textContent.trim())
    //   .join(' > ')
    // const title = ele.querySelector('.sc-fzXfPJ').textContent
    // const sku = ele.querySelector('a').href.split('/').pop()
    // const price = ele.querySelector('.sc-fzXfPM').textContent.replace(/\D+/g, '')
    const breadcrumb = [...document.querySelectorAll('.sc-LzLtS li')]
      .map(($li) => $li.textContent.trim())
      .join(' > ')
    const items = [...document.querySelectorAll('.product-card__vertical')].map((ele) => {      
      const title = ele.querySelector('.sc-fzXfNM').textContent      
      const sku = ele.getAttribute('href').split('/').pop()      
      const price = ele.querySelector('.sc-fzXfNc').textContent.replace(/\D+/g, '')      
      return new Product({ title, sku, price })
    })
    tracker('viewItemList', {
      breadcrumb: breadcrumb,
      items: items,
    })    
  },
})

console 印出來的:

> fire all viewItemList (187968d1-0fc3-47eb-8780-9e8e188eab30)
> {breadcrumb: '首頁 > 男裝下身 > 丹寧短褲', items: Array(11)}
> send gtag event 'view_item_list' to ["AW-781127587"]
> {item_list_id: '首頁 > 男裝下身 > 丹寧短褲', item_list_name: '首頁 > 男裝下身 > 丹寧短褲', items: Array(11), send_to: Array(1), currency: 'TWD'}
> send gtag event 'view_item_list' to ["G-F3CYVHGRHQ"]
> {item_list_id: '首頁 > 男裝下身 > 丹寧短褲', item_list_name: '首頁 > 男裝下身 > 丹寧短褲', items: Array(11), send_to: Array(1), currency: 'TWD'}
> send gtag event 'view_item_list' to ["UA-34980571-1"]
> {event_category: 'engagement', event_label: '首頁 > 男裝下身 > 丹寧短褲', send_to: Array(1), currency: 'TWD'}
> fire google viewItemList event
> fire unitrack viewItemList event

新人訓練

mentor 的教學文件

第一堂: 第二堂:https://docs.google.com/document/d/151W21U2bT5oc6Z1HT6loNYIA0t0gGUCHEhCPjgjSvp8/edit#

塔圖新人訓練

https://sites.google.com/tagtoo.com/tagtoo-training/%E6%96%B0%E4%BA%BA%E8%A8%93%E7%B7%B4step-1/rd%E6%96%B0%E4%BA%BAstep-1-2?authuser=5

找尋網頁

如果今天想要找某個網站的特定網頁,可以在該網站的console頁面,輸入下面的 code,就可以印出包含此keyword的所有連結 ex. 我想找到該網站,所有包含 detail 的網址

var keyword = "detail";
var links = document.querySelectorAll('a[href*="' + keyword + '"]');
for (var i = 0; i < links.length; i++) {
  console.log(links[i].href);
}

正式開始寫 - 第一間 2878

1. 7 行碼確認

確認客戶是否有把 7行碼 埋到官網 -> 直接在 console 輸入 tagtoo,看有沒有值出現,或者是用 network 查看也可以

2. 要渠道權限

跟 patrick 要 facebook 、 GADs 、 GA 權限,要從後台看新客戶的像素有哪些

Ps. 這個網頁有塔圖所有帳號密碼:https://docs.google.com/document/d/13Fyvh8xi1J1blpIfjH2drC0xFCaa7u11o_RzHsthRww/edit#,有所有的帳號/密碼設定

3. 設定 muffet 檔案

3-1 新增客戶

$ npm run create-ec 2878

這樣會多一個資聊夾,這個資料夾就是我們要寫爬蟲的地方

3-2 安裝 npm

這個可以安裝 muffet 所需要的插件

$ npm install

3-3 npm run preview 2878

接下來我們就可以在終端機執行這一段程式碼

$ npm run preview 2878

Ps. 啟動後,可以按鍵盤上的快捷鍵,q 是離開啟動環境,s會印出一段程式

按下 s 後,會印出下面這一段:

window.tagtoo_advertiser_id = 2878;
window.tgDataLayer = window.tgDataLayer || [];
function tgk() { window.tgDataLayer.push(arguments); }
const $script = document.createElement('script');
$script.charset = 'UTF-8';
$script.src = `http://localhost:3000/${window.tagtoo_advertiser_id}.js`;
document.body.appendChild($script);
tgk('autoRun');

3-4 使用 CJS

接著我們就可以打開 CJS 插件,並且把剛剛那一段 code,貼上去後啟動,這樣就可以在本地端開發了。
Ps. 這樣做的原因,是因為我們現在還沒有 push 到 GCP 上,所以如果用原本的 ModHeader 的方法,會無法在本地端觸發那些事件

4. ad online check 確認這過網站有哪些像素

這個表單也可以參考,這裡有所有客戶的像素表單整理 https://docs.google.com/spreadsheets/d/1eW2kmFs1NU2zSYrlgDw-QrCCNUH7kOrAiGBeCVRo5KI/edit#gid=1592073239

5. 設定Dashboard-legacy 的 media/ad/track.js,需要再用 minify-track 這個 script 把 track.js 做檔案壓縮

照著以下步驟完成:

5-1 找到 dashboard-legacy repo

從 dashboard-legacy 這個 repo 找到 media/ad/track.js 檔案 -> repo 連結在這:https://github.com/Tagtoo/dashboard-legacy

5-2 找到 track.js

照個這個路徑,DASHBOARD-LEGACY/media/ad/track.js,並把負責的 ec_id 加入檔案,像下面這樣:

>  var ecId = window.tagtoo_advertiser_id.toString();
>   switch (ecId) {
>       case '49':
>       case '94':
>       case '100':
>       case '107':
>       ...
>       case '2878':      => 我這次的新客戶是 2878
>  }

壓縮檔案

執行下面這一段指令

$ npm run minify-track

這樣輸入後,就會完成把 track.js 檔案壓縮

發 PR 注意事項

  • branch 名稱可以用 EC_ID-john 這樣的格式,或者單純 EC_ID 就可 - ex. 2878
  • PR 名稱就用 feat 那個 - ex. feat(ec/2878): new client
  • PR 的內容區塊要寫完整一點,以這個為例:https://github.com/Tagtoo/muffet/pull/2681
  • 因為塔圖要設定自動跑 CI/CD,如果今天沒有設定好,會自動發生 error,如果PR發生錯誤,可以點擊GCB DETAIL,會進到他跑 CI/CD 的地方,他顯示你哪邊有錯誤,根據你錯的地方再修改就好

Ps. patrick 會幫你開塔圖帳號的GCP,記得 chrome 的使用者要先改成塔圖的,要不然他會說你沒權限

7. muffet 設定細節

7-1 trackProduct

tracker(‘viewItem’, product) tracker(‘addToCart’, product) tracker(‘addToWishlist’, product)

7-2 trackCartPage

tracker(‘trackCart’, cart)
tracker(‘viewCart’, cart)
tracker(‘checkout’, cart)
tracker(‘addShippingInfo’, cart)
tracker(‘addPaymentInfo’, cart)

7-3 trackCheckout

tracker(‘checkout’, cart)

7-4 trackPurchase

tracker(‘purchase’, cart)

7-5 trackSearch

tracker(‘search’, searchKey)

7-6 trackRegister

tracker(‘register’, ‘member’) tracker(‘register’, ‘line’) tracker(‘register’, ‘facebook’)

7-7 trackLogin

tracker(‘login’, ‘member’) tracker(‘login’, ‘line’) tracker(‘login’, ‘facebook’)

這是幾個基本的是事件設定,這邊就先簡單提到,把所有tracker 設定好後,就可以發 PR 了

一些設定備註

如果今天有一個案例,有兩個網址如下:

產品頁:https://www.jdgift.com.tw/products/plaid-picnic 搜尋頁:https://www.jdgift.com.tw/products?query=%E7%B4%85%E8%B1%86

可以看到兩個網頁都有包含 products,所以如果你今天產品頁的觸發是像這樣

> pageView.path.contains('products')

就可能造成當你在做搜尋動作的時候,也觸發了產品頁的觸發動作,因此我們可以加上這一段

> pageView.path.contains('products').and(pageView.path.not.contains('?query'))

這樣就可以解決兩個網址都有一樣字串的問題。

前人寫的案例:

> trigger: pageView.path.contains('/Product').and(pageView.path.not.contains('/Brand')),

額外注意事項

  1. facebook 事件是 OP 要設定,如果他們沒設定,要記得跟他們溝通
  2. google ADwords 是 teddy 會設定,不過他不會馬上設定,所以最後在把轉換像素設定好就好

登入公司 GTM 的文件

申請帳號

先到這個 google sheet,輸入自己想要開通的帳號,patrick 會定時的幫上面的申請者開通 https://docs.google.com/spreadsheets/d/1bBdlGMPxZ5ZmfWSageHL2_lKFgArsbl8RKzcjIS6F8w/edit#gid=807184972

下載插件

這個插件是 tagtoo 專門做的插件,登進去後 (輸入剛進公司要填寫的dashboard帳號密碼),就可以開啟 https://chrome.google.com/webstore/detail/tagtoo-web-extension/klcndokhmdicpjgjncfchdehkhbhpfbi?hl=zh-TW&authuser=0

登入

先進到 Google Tag Manager 的頁面,直接點擊這個連結就好 - https://tagmanager.google.com/#/home,接著點擊右邊的頭像,我們來新增一個登入帳號,接著到輸入帳號密碼環節,我們來點開剛剛載好並登入過的 tagtoo插件。

首先點擊左上角的 icon,會抬出側邊欄,有三個選項,一個是 產生轉址連結,一個是 檢查追蹤碼,最後一個是 登入公用帳號,我們要用的就是最後一個,點下去,選擇google Analytics,還記得此時我們還在GA輸入帳號密碼的頁面嗎?我們只要點擊插件的 登入,一直點下去,輸入欄位就會自動把登入的帳號密碼補上。

驗證碼

剛剛完成了帳號密碼的輸入,到最後一步驟會要你輸入驗證碼,點下面的連結,會有驗證碼的 qrcode,打開手機掃描,就可以拿到登入驗證碼了。 https://docs.google.com/document/d/1z7O38EcWbnMqDZoX0Rz70QO85xf1Ss-tLf8k9Ofsg90/edit