# B-6 販售產品詳細頁
> [name=JasonWu]
###### tags: `CP頁`
------------------------------------------------------------------------
## API 清單
No | API | Desc | 執行順序 | 執行條件 | 參考
---|----------|---------------------|----------|--------------------|--------
1 | [URI-A-02](https://hackmd.io/Cbw7V8AETEOzCk4s7TgfwA) | 多語標籤 | 1 | page init |
2 | [URI-B-03](https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA) | 取得型錄相關資料 | 1 | page init | 主要 api
3 | [URI-B-06C](https://hackmd.io/FDa_t2QwRxKoBimH6mW4qQ) | 商機Inquiry Product | 1 | page init | 無
4 | [URI-B-05](https://hackmd.io/ogBAYWnFQeGUTgYPo9aZPg) | 取得推薦相關型錄 | 2 | page init | 相關輸入條件會與 URI-B-03 有關
5 | [URI-B-04](https://hackmd.io/WTAzi5tXQBuakQV1tkgATw) | 取得廠商相關資料 | 3 | 點選廠商 tab | 呼叫廠商 api
6 | [URI-B-07](https://hackmd.io/03JtG4EZT8aAqqnddSgWTQ) | 取得販售相關資訊 | 3 | 點選 Shipping tab<br>切換販售運費國家 | 無
7 | [URI-C-02](https://hackmd.io/OFJ68AIxTfePBEeKqlGa-w) | 取得燈箱相關資訊 | 3 | 點選型錄認證/公司認證開啟燈箱 | 無
8 | [URI046](https://hackmd.io/C0_27LhmTD6m9qV_DahhTQ) | 取得 SEO META 資料 | 4 | SEO META 相關內容 | 無
## 頁面相關路徑
區塊 | 說明 | 相關路徑
-------------|--------------------------------|------------------------------
圖片與多媒體 | 3D 小圖 | https://cs01.ttstaging.com.tw/front/dea1e3c/tteng/images/icon_view3D_60.png
圖片與多媒體 | 360 小圖 | https://cs01.ttstaging.com.tw/front/dea1e3c/tteng/images/icon_view360_60.png
圖片與多媒體 | Video 小圖 | https://cs01.ttstaging.com.tw/front/dea1e3c/tteng/images/icon_viewVideo_60.png
圖片與多媒體 | 沒有圖片時的大圖 | https://cs01.ttstaging.com.tw/front/dea1e3c/tteng/images/public/360x360.png
圖片與多媒體 | 沒有圖片時的小圖 | https://cs01.ttstaging.com.tw/front/dea1e3c/tteng/images/public/blank100x100.jpg
運費資料 | DHL | https://cs01.ttstaging.com.tw/front/24f1e33/tteng/images/icon/logo_ship_s_dhl.jpg
運費資料 | FEDEX | https://cs01.ttstaging.com.tw/front/24f1e33/tteng/images/icon/logo_ship_s_fedex.jpg
運費資料 | TNT | https://cs01.ttstaging.com.tw/front/24f1e33/tteng/images/icon/logo_ship_s_tnt.jpg
運費資料 | EMS | https://cs01.ttstaging.com.tw/front/24f1e33/tteng/images/icon/logo_ship_s_ems.jpg
運費資料 | UPS | https://cs01.ttstaging.com.tw/front/24f1e33/tteng/images/icon/logo_ship_s_ups.jpg
運費資料 | Other | https://cs01.ttstaging.com.tw/front/24f1e33/tteng/images/icon/logo_ship_s_other.jpg
payment term | alipay | https://cs01.ttstaging.com.tw/front/da74d3a/tteng/images/icon/alipay.png
payment term | paypal | https://cs01.ttstaging.com.tw/front/da74d3a/tteng/images/icon/paypal.png
payment term | card | https://cs01.ttstaging.com.tw/front/da74d3a/tteng/images/icon/card.png
payment term | tenpay | https://cs01.ttstaging.com.tw/front/da74d3a/tteng/images/icon/tenpay.png
payment term | unionpay | https://cs01.ttstaging.com.tw/front/da74d3a/tteng/images/icon/unionpay.png
payment term | hitrust | https://cs01.ttstaging.com.tw/front/da74d3a/tteng/images/icon/hitrust.png
payment term | card_ae | https://cs01.ttstaging.com.tw/front/da74d3a/tteng/images/icon/card_ae.png
payment term | googlepay | https://cs01.ttstaging.com.tw/front/da74d3a/tteng/images/icon/googlepay.png
payment term | applepay | https://cs01.ttstaging.com.tw/front/da74d3a/tteng/images/icon/applepay.png
## 原頁面網址
- https://www.ttstaging.com.tw/product/1893926
## 頁面輸入 URL 解析
- 進入頁面方式有兩種
- /product/{id}
- 範例 : https://www.ttstaging.com.tw/product/1893926
- id : 型錄 id,為純數字
- /product/{safe-name}-{id}.html
- 範例 : https://www.ttstaging.com.tw/product/tdk-blu-ray-dl-4x-50gb-809803.html
- safe-name : 型錄名稱,為純文字,且型錄名稱會將英文轉為小寫,空白、特殊符號等字元替換為 - 號,例如 型錄名稱 為 **TDK Blu Ray DL 4X 50GB** 將會轉為 **tdk-blu-ray-dl-4x-50gb**
- id : 型錄 id,為純數字
- id 為主要參數,會應用在後面的 api 中
## 頁面說明


### 頁面區塊說明
- B-06-01
- 共用Header Service元件, 參考[`L01`](https://docs.google.com/spreadsheets/d/1FfU1adulMCzdWNQNjagsm5rHYhokRwhKfzY-Lurvw9Q/edit#gid=1382704913)
- B-06-02
- 麵包屑,請參考[URI-B-03](https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA?view#麵包屑)
- B-06-03
- 型錄詳細資料,請參考[URI-B-03](https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA)
- B-06-04
- 商機Inquiry Product,請參考[URI-B-06C](https://hackmd.io/FDa_t2QwRxKoBimH6mW4qQ?both)
- B-06-05
- 推薦型錄&小額採購區,請參考[URI-B-05](https://hackmd.io/ogBAYWnFQeGUTgYPo9aZPg)
- B-06-06
- 參考首頁Footer
- SeoMeta 資訊
- 透過 api 取得頁面所需的 meta 資訊
- 麵包屑
- 透過 api 取得麵包屑
- 圖片與多媒體
- 透過 api 取得圖片、多媒體資訊
- 型錄主要資訊
- 透過 api 取得產品名稱, 型號資訊等資訊
- tab 區塊,切換顯示
- 透過 api 決定顯示那些顯示 tab
- 詳細資料
- 透過 api 取得產品詳細資訊
- 參考廠商資訊 api,顯示對應的廠商資訊
- 參考運費計算 api,顯示對應的運費資訊
- 聯繫廠商
- 參考聯繫廠商 api,顯示相關畫面
- 推薦型錄
### 頁面欄位說明
#### SeoMeta 資訊
- 舊版頁面相關資訊透過 xml 傳遞

- 產出 html 頁面時,需要將相關資訊放入 meta tag 中

- ~~顯示邏輯~~
- ~~參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA?view#meta1~~
- ~~取得 data 中的 meta array~~
- ~~邏輯與格式~~
- ~~將 array 中的內容依次放入~~
- ~~格式固定為 <meta name="{meta.name}" content="{meta.content}">~~
- 透過 [URI046](https://hackmd.io/C0_27LhmTD6m9qV_DahhTQ) 取得頁面相關的 meta 資料
- 呼叫相關 api 路徑如下: ${TT-API網址}/domains/seo/{seoType}/{pageType}/{did}/{rowId}
- 參數 seoType 帶入 "1"
- 參數 pageType 帶入 "PRODUCT_DETAIL"
- 參數 did 帶入頁面 {did}
- 參數 rowId 帶入型錄 id {id}
- 解析回傳的 json 內容,並寫入頁面原始檔中
#### 麵包屑

- 顯示邏輯
- 參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA?view#%E9%BA%B5%E5%8C%85%E5%B1%911
- 取出 data 中的 breadInfo array
- 邏輯與格式
- 1 Home
- 顯示 **Home** 連結
- Home 為多語,{lang.home}
- Home 的 url 是固定的,連結到該 domain 首頁
- 2 路徑連結
- 顯示 **二碼**,**四碼**,**六碼** 的連結
- 依照 {breadInfo.seq} 順序,依次顯示 breadInfo 資料
- 連結名稱套用 {breadInfo .name}
- 點選後的 url 套用 {breadInfo.url}
- 3 型錄的名稱
- 顯示 **型錄名稱**
- 套用 {productInfo.productName}
- 操作邏輯
- 點選麵包屑的連結,redirect 到對應的頁面
- 產品名稱不需要連結
#### 圖片與多媒體

- 顯示邏輯
- 圖片部分參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA#%E5%9C%96%E7%89%87%E8%B3%87%E6%96%99
- 多媒體部分參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA#%E5%A4%9A%E5%AA%92%E9%AB%94%E8%B3%87%E6%96%99
- 取出 data 中的 sliderGalleryInfo array 與 multimediaInfo object
- 邏輯與格式
- 1 區塊大圖
- 找出主要圖片
- 找出 sliderGalleryInfo 中 {sliderGalleryInfo.hasPrimary} = 1 的那一筆
- 顯示該筆資料的 {sliderGalleryInfo.img360_360Url}
- 如果找不到對應的圖片,將會顯示預設圖
- 大圖 url : [`沒有圖片時的大圖`](#頁面相關路徑)
- 小圖 url : [`沒有圖片時的小圖`](#頁面相關路徑)

- 2 Enlarge 按鈕
- 顯示操作按鈕,供使用者點選
- Enlarge 為多語,{lang.ez_enlarge}
- 3 多媒體圖示
- 顯示多媒體圖示,分為三大類:**3D**, **360**, **Video**,對應上圖區塊 3 中由左至右的三個圖示
- 如果 {multimediaInfo.multimedia_3d_url} != '',需要顯示 **3D** 的圖示 : [`3D 小圖`](#頁面相關路徑)
- 目前 3D 標題 {multimediaInfo.multi_media_3d_title} 並未使用,後續如果不需使用,此 api 欄位可移除
- 如果 {multimediaInfo.multimedia_360_url} != '',需要顯示 **360** 的圖示 : [`360 小圖`](#頁面相關路徑)
- 目前 360 標題 {multimediaInfo.multi_media_360_title} 並未使用,後續如果不需使用,此 api 欄位可移除
- 如果 {multimediaInfo.video_url} != '',需要顯示 **Video** 的圖示 : [`Video 小圖`](#頁面相關路徑)
- 4 小圖 slider
- 依次顯示 {sliderGalleryInfo.img100_100Url} 的小圖
- 提供 slider 捲動效果,相關內容參考對應操作邏輯
- 5 分享按鈕
- 使用外部套件 [`addthis`
](https://www.addthis.com/)
- 可參考 cms-front/src/main/webapp/site/tteng/xsl/share.xsl

- 操作邏輯
- 點選 多媒體3D 或 多媒體360 圖示,顯示相關操作燈箱

- 帶入目前 multimediaInfo 內的資料
- 1 透過 iframe,將對應的 html 放入燈箱內
- 點選 3D 時,iframe 顯示 {multimediaInfo.multimedia_3d_url}
- 點選 360 時,iframe 顯示 {multimediaInfo.multimedia_360_url}
- 2 關閉燈箱按鈕,點選後關閉燈箱
- 3 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕
- 點選 多媒體 Video 圖示,顯示相關操作燈箱

- 帶入目前 multimediaInfo 內的資料
- 1 透過 iframe,將對應的影片放入燈箱內
- 如果影片是 youtube,可使用 youtube
- 如果不是,直接將對應內容放入 iframe
- 2 關閉燈箱按鈕,點選後關閉燈箱
- 3 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕
- 當關閉燈箱時,如果影片正在撥放,此時需要停止撥放影片
- 目前舊版程式對 youtube 影片有做此處理,關閉燈箱時會呼叫 youtube 的 stopVideo 來停止撥放
- 點選 Enlarge,顯示相關操作燈箱

- 帶入目前 sliderGalleryInfo 內的資料
- 1 顯示目前該型錄的標題
- 2 顯示 sliderGalleryInfo 中的所有 {sliderGalleryInfo.img100_100Url}
- 3 顯示當前對應顯示的 {sliderGalleryInfo.imgOriUrl},預設是 {sliderGalleryInfo.hasPrimary} = 1 的原始圖
- 4 此為 jquery.smoothZoom 套件 的效果,可局部放大原始圖片內容
- 5 關閉燈箱按鈕,點選後關閉燈箱
- 6 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕
- 舊版採用 jquery.smoothZoom 套件,載入 jquery.smoothZoom.css 與 jquery.smoothZoom.js
- 當觸發了開啟 Enlarge 燈箱時,需要暫停型錄小圖 slider 的自動捲動效果;關閉時再恢復型錄小圖 slider 的自動捲動效果
- 滑鼠移動到大圖上,會有 Zoom in 的效果

- 首先判斷當前瀏覽器頁面的寬度是否 > 768 (rwd 不需要)
- 再來判斷目前選取的圖片是否長寬都 > 360 (圖太小不需要)
- 以上條件都符合時,才會觸發 Zoom in 效果
- 帶入當前大圖 {sliderGalleryInfo.imgOriUrl} 當作縮放的原始圖
1 透過 jquery.elevateZoom.cover 套件,在原始大圖的地方顯示放大區塊來源
2 透過 jquery.elevateZoom.cover 套件,顯示對應放大區塊的圖
- 舊版採用 jquery.elevateZoom.cover 套件,載入 jquery.elevateZoom.cover.js
- 當觸發了 Zoom in 效果時,需要暫停型錄小圖 slider 的自動捲動效果;離開時再恢復型錄小圖 slider 的自動捲動效果
- 型錄小圖 slider 自動捲動效果
- slider 超過兩則圖片時,需要提供 slider 效果
- 舊版採用 slick slide 套件,載入 slick-utils.js
- 舊版設定開啟自動撥放 (autoplay: true)
- 舊版設定自動捲動效果的時間是 4 秒 (autoplaySpeed: 4000)
- 首尾相接的效果,到最後一張圖時,接在後面的是第一張圖 (infinite: true)
- 型錄小圖 slider 手動捲動效果

- slider 超過兩則圖片時,需要提供 slider 效果
- 舊版採用 slick slide 套件,載入 slick-utils.js
- 點選向左或是向右,型錄小圖 slider 會捲動到前一筆或是後一筆
- 捲動的同時,上方型錄大圖也會跟著切換為當前第一張小圖對應的大圖
- 點選小圖,會將上面的大圖換為點選對應的大圖
- 點選下方小圖,會將上面的大圖換為點選對應的大圖
- 點選後,小圖 slider 的 index 會切換到目前點選的項目
#### 型錄主要資訊

- 顯示邏輯
- 型錄相關內容(1,2,3,4,5,6,7,10,11,12,13,14,15區塊)參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA#%E5%9E%8B%E9%8C%84%E8%B3%87%E6%96%99
- 價格表內容(8)參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA#%E5%83%B9%E6%A0%BC%E8%A1%A8
- 規格屬性選項內容(9)參考
https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA#%E8%A6%8F%E6%A0%BC%E5%B1%AC%E6%80%A7%E9%81%B8%E9%A0%85
- 取出 data 中的 productInfo object, priceRangeInfo array, 與 spec4SelectInfo array
- 邏輯與格式
- 1 折扣與折扣剩餘日期
- 先判斷 {productInfo.hasDiscount} == 1(有折扣)
- 再判斷 {productInfo.discountStartDate} 與 {productInfo.discountEndDate} 跟今天日期的比較,判斷是否在折扣日內
- {productInfo.discountStartDate} 與 {productInfo.discountEndDate} 回傳的資料型態為 long,對應 linux timestamp,例如 1633332979000,代表台灣時間 2021-10-04 15:36:19
- 再判斷 {productInfo.discount} 是否有值且 {productInfo.discount} > 0.0
- 上面三點都成立,代表有折扣,折扣值 = {productInfo.discount}
- ~~接下來判斷 剩餘日期,{productInfo.discountDayLeft} 是否有值,有的話才顯示 XX Days Left~~
- 如果有折扣,且 {productInfo.discountEndDate} != null,此時需要計算對應的折扣剩餘日期
- 用目前的時間 - {productInfo.discountEndDate} 來計算,如果剩餘的時間超過一日以上,顯示 xx Days Left 
- 如果剩餘的時間在一日內,顯示剩餘的時分秒 
- Days Left 為多語,{lang.ez_daysleft}
- 另外補上 {productInfo.isFreeShipping} 判斷,如果 {productInfo.isFreeShipping} == 1,顯示[免運費圖片](https://cs01.ttstaging.com.tw/front/497a7e5/tteng/images/icon_shipping2.png),如果要沿用,請將此圖檔下載後套用,多語請參考 {lang.ez_freeshipping}

- 2 型錄標題
- {productInfo.productName}
- 3 型錄型號
- 標題為多語,{lang.modelno}
- {productInfo.modelNo}
- 沒有資料時不顯示本欄
- 4 產地
- 標題為多語,{lang.madein}
- {productInfo.madeIn}
- 沒有資料時不顯示本欄
- 5 廠商資訊

- 5-1 標題為多語,{lang.supplier}
- 5-2 顯示廠商名稱,{productInfo.supplierName}
- ~~5-2 檢查 {productInfo.epUrl} 是否存在,如果存在,廠商名稱加上 url 連結,連結的 url = {productInfo.epUrl}~~
- 檢查 {productInfo.supplierUrl} != '',有值代表該廠商相關公司網址,此時廠商名稱加上 url 連結,連結的 url = {productInfo.supplierUrl}
- ~~5-3 檢查 {productInfo.epUrl} 是否存在,如果存在才顯示 More About This Product 項目,點選的 url = {productInfo.epUrl} + '/product/' + {productInfo.productId}~~
- 5-3 檢查 {productInfo.epUrl} 是否存在,如果存在才顯示 More About This Product 項目,點選的 url = {productInfo.epUrl}
- 5-3 More About This Product 為多語,{lang.ez_moreaboutproduct}
- 目前 api 會額外回傳以下相關資料
- 公司 ep 年資:{productInfo.supplierEpYear}
- 公司認證小圖清單:{productInfo.supplierIcon}
- 公司營業類型:{productInfo.supplierBusinessType}
- 公司是否有 tts 認證:{productInfo.supplierTTSIcon}
- 如果 {productInfo.supplierTTSIcon} == 1,代表對應的公司有 tts 認證,此時需要顯示 tts 認證的圖片

- 6 原始價格
- 標題為多語,{lang.ez_unitprice}
- 顯示 {productInfo.priceCurrencyName} + {productInfo.priceMin} + '~' + {productInfo.priceMax} + '/' + {productInfo.unitName}
- 參考項目 1 的判斷,如果該型錄有折扣,需要在價格上標註刪除線,代表原價有打折
- json 提供的是 USD 金額,如果頁面的幣別有替換,那顯示時需要將金額從 USD 換算為對硬幣別的金額,幣別顯示也要改為頁面的幣別
- 7 折扣價格
- 標題為多語,{lang.ez_discountprice}
- 參考項目 1 的判斷,如果該型錄沒有折扣,本項不顯示
- 顯示 {productInfo.priceCurrencyName} + ({productInfo.priceMin} * {productInfo.discount}) + '~' + ({productInfo.priceMax} * {productInfo.discount}) + '/' + {productInfo.unitName}
- json 提供的是 USD 金額,如果頁面的幣別有替換,那顯示時需要將金額從 USD 換算為對硬幣別的金額,幣別顯示也要改為頁面的幣別
- 8 價格表

- 以表格的方式顯示價格表,依次解析 priceRangeInfo array 的內容
- 如果 priceRangeInfo 沒有資料,這個區塊不顯示
- 8-1 標題為多語,{lang.price}
- 8-2 表格標題多語列表如下
- Quantity : {lang.ez_quantity}
- Price : {lang.price}
- Processing Time : {lang.ez_processingtime}
- 8-3 表格內容對應欄位放置對應如下
- Quantity : {priceRangeInfo.quantityRange}
- Price : {priceRangeInfo.price}
- Processing Time : {priceRangeInfo.prepareDay} + {lang.ez_days}
- {priceRangeInfo.price} 內容包含幣別與金額,如果頁面的幣別有替換,那顯示時需要將金額從 USD 換算為對硬幣別的金額,幣別顯示也要改為頁面的幣別
- 8-4 顯示 Request for Quotation 連結
- 連結名稱為多語,{lang.requestforquotation}
- 連結網址固定,格式為 "/latest-price/step1?productId=" + {productInfo.productId}
- 9 規格屬性

- 解析 spec4SelectInfo array 中的內容,組成對應的下拉選單
- 如果 spec4SelectInfo 沒有資料,這個區塊不顯示
- 9-1 顯示名稱 {spec4SelectInfo .name}
- 9-2 產出對應的下拉選單
- 預設選項(Please Select)為多語,{lang.ez_pleaseselect}
- 每個項目的 value = {spec4SelectInfo.options .id}
- 每個項目的 name = {spec4SelectInfo.options .name}
- 每個項目有可能有圖片,對應大圖圖片 = {spec4SelectInfo.options.pcImgUrl},對應小圖圖片 ={spec4SelectInfo.options.mobileImgUrl},對應原圖圖片 = {spec4SelectInfo.options.oriImgUrl}
- 圖片的用途請參考下面的操作邏輯
- 9-3 Quantity 為多語,{lang.ez_quantity}
- 9-4 可以透過 + 跟 - 的按鈕調整 Quantity 的數量,調整 Quantity 數量的邏輯請參考下面的操作邏輯
- ~~9-5 包含可以訂購的數量, UnitName 與 available 的多語~~
- ~~可訂購數量為 {productInfo.ezMaxOrderCount}~~
- ~~unitName 為 {productInfo.unitName}~~
- ~~available 為多語,{lang.ez_available}~~
- 9-5 顯示可訂購數量, 單位名稱 與 available 文字的多語
- 可訂購數量為 {productInfo.ezMaxOrderCount}
- 單位名稱 為 {productInfo.unitName}
- available 為多語,{lang.ez_available}
- 10 Total Price
- 標題為多語,{lang.ez_totalprice}
- 金額顯示格式為 幣別 X 個數 = 金額 / 單位
- 幣別 : {productInfo.priceCurrencyName}
- 個數 : 對應欄位 9 的 Quantity 數量,預設為 1
- 金額 : 透過計算而得,相關邏輯請參考下面的操作邏輯
- 單位 : {productInfo.unitName}
- 如果頁面的幣別有替換,那顯示時需要將金額從 USD 換算為對硬幣別的金額,幣別顯示也要改為頁面的幣別

- 11 Payment 區塊
- 顯示邏輯

- 11-1 Payment 圖示
~~- 取得 data 中的 paymentTermInfo array~~
~~- 依照 {paymentTermInfo.code} 決定顯示什麼圖~~
~~- [`Paypal`](#頁面相關路徑)~~
~~- [`支付寶 alipay`](#頁面相關路徑)~~
~~- [`信用卡 card`](#頁面相關路徑)~~
~~- [`財付通 tenpay`](#頁面相關路徑)~~
~~- [`銀聯卡 unionpay`](#頁面相關路徑)~~
~~- [`網際威信 hitrust`](#頁面相關路徑)~~
~~- [`信用卡(AE) card_ae`](#頁面相關路徑)~~
~~- [`google pay`](#頁面相關路徑)~~
~~- [`apple pay`](#頁面相關路徑)~~
- 11-1 Payment 圖示
- 取得 data 中的 paymentInfo object
- 解析 {paymentInfo.paymentTerms} 決定顯示什麼圖:內容為透過逗號分隔的相關付款圖示,依照對應的格式顯示對應的圖
- [`Paypal`](#頁面相關路徑)
- [`支付寶 alipay`](#頁面相關路徑)
- [`信用卡 card`](#頁面相關路徑)
- [`財付通 tenpay`](#頁面相關路徑)
- [`銀聯卡 unionpay`](#頁面相關路徑)
- [`網際威信 hitrust`](#頁面相關路徑)
- [`信用卡(AE) card_ae`](#頁面相關路徑)
- [`google pay`](#頁面相關路徑)
- [`apple pay`](#頁面相關路徑)
- 11-2 More
- More 為多語,{lang.more}
- 點選請參考下面的操作邏輯
- 11-3 詳細內容
- We also supports 目前是固定的
- 線下支付相關名稱取自於 {paymentInfo.offlineMethod}
- for offline orders. 目前是固定的
- Min. Order: 為多語,{lang.ez_minorder}
- 最小訂購量為 {paymentInfo.minOrderQuantity}
- 12 Buy Now 按鈕
- Buy Now 為多語,{lang.ez_buynow}
- 點選相關邏輯請參考下面的操作邏輯
- 13 Contact Supplier 按鈕
- Contact Supplier 為多語,{lang.contactsupplier}
- 點選相關邏輯請參考下面的操作邏輯
- 14 Add to Cart
- Add to Cart 為多語,{lang.ez_addtocart}
- 點選相關邏輯請參考下面的操作邏輯
- 15 Add to Favorites
- Add to Favorites 為多語,{lang.addtofavorites}
- 如果該型錄已經加入過 favorites 的話,要顯示 Go to Favorites,{lang.gotofavorites}
- 點選的連結請參考條列頁的加到最愛與 redirect 到最愛的邏輯
- 警語相關
- 取得 {productInfo.catalogStandardCid},會影響到本頁是否要顯示警語
- 其他欄位
- ~~目前 api 會傳遞部分欄位,目前前台顯示並未特別輸出,但對應的 ep 畫面存在,這些欄位目前 api 還是會傳,再依後續邏輯判斷是否要保留~~
- ~~{productInfo.hasNewest},是否需要顯示 New Tag,0(否)/1(是)~~
- ~~{productInfo.hasFeatured},是否需要顯示 Featured Tag,0(否)/1(是)~~
- {productInfo.hasSelling},是否販售,productInfo.hasSelling == 1 代表販售,進入販售產品詳細頁
- 操作邏輯
- 規格屬性,下拉選單選擇

- 對應頁面 9-2 所描述的操作方式
- 如果選取下拉選單項目對應的 {spec4SelectInfo.options.pcImgUrl} != '',代表這個規格屬性有對應圖片,要將此圖片加入 sliderGalleryInfo 中
- 例如原本該型錄有兩張圖,點選了一個有圖的規格屬性,此時型錄要顯示三張圖,前兩張是原本該型錄 sliderGalleryInfo 中的圖,要再額外加一張規格屬性的圖
- 上面的大圖對應 {spec4SelectInfo.options.pcImgUrl}
- 新加入的圖同樣具備原本的圖片效果(Zoom in, Enlarge)
- 目前系統會將 {spec4SelectInfo.options.pcImgUrl} 帶入,下方的 slider 沒有放入小圖,後續如果需要擴充加入小圖的功能,小圖可以使用 {spec4SelectInfo.options.mobileImgUrl}
- 規格屬性,有價格表的情況下,改變 Quantity

- 對應頁面 9-4 所描述的操作方式
- 先參考上方的 price 表,判斷 quantity 介於哪個區間內,然後取得金額
- 以上圖為例,當 quantity = 5,對應的金額是 USD 105.56
- 以上圖為例,當 quantity = 11,對應的金額是 USD 94.53
- quantity 需要做最大值的檢查,不能超過最大值
- 以上圖為例,quantity 的最大值 = 100
- 變更 quantity 時,Total Price 區塊需要重新計算,金額會從對應的 price 表取得
- 以上圖為例,當 quantity = 5,此時 Total Price = USD 105.56 X 5 = USD 527.80
- 以上圖為例,當 quantity = 11,此時 Total Price = USD 94.53 X 11 = USD 1,039.83
- 規格屬性,沒有價格表的情況下,改變 Quantity
- 對應頁面 9-4 所描述的操作方式
- quantity 需要做最大值的檢查,不能超過最大值
- 變更 quantity 時,Total Price 區塊需要重新計算
- 由於沒有價格表,代表此型錄的產品是單一價格,所以修改 quantity 時,直接使用 {productInfo.priceMin} 來計算 Total Price
- 點選 Buy Now 按鈕

- 先檢查該型錄是否有 規格屬性 ,如果有規格屬性,那必須每一項下拉選單都要有值,如果沒有值,顯示提示訊息
- 通過檢查,將會執行以下動作
1. 將型錄加入購物車(Add to cart)
2. redirect 到固定網址:{shopping domain}/secured/shoppingcart/step1
- 點選 11-2 的 More
- 顯示 11-3 的資訊
- 點選 Contact Supplier 按鈕
- 跳轉到下面的 "聯繫廠商" 區塊
- 點選 Add to Cart
- 參考之前條列頁的加入購物車功能
- 加入購物車功能,參考 header service 的 add to cart 部分
- 呼叫 header service,相關 post url
- uat : https://header-service.ttstaging.com.tw/cart-receiver/add-item
- prod : https://header-service.taiwantrade.com/cart-receiver/add-item
- 透過 post 傳入相關參數,參數範例如下

- productId 是要加入購物車的型錄 id,{productInfo.productId}
- languageId 是該型錄的語系,{productInfo.languageId}
- spec 是頁面參數,對應選擇下拉的 spec 的值
- cartSpecId : 對應選擇項目的 {spec4SelectInfo.sid}
- cartSpecName : 對應選擇項目的 {spec4SelectInfo.name}
- cartSpecDetailId : 對應選擇項目的 {spec4SelectInfo.options.id}
- cartSpecDetailName : 對應選擇項目的 {spec4SelectInfo.options.name}
- quantity 是頁面參數,對應頁面的 Quantity 值
- shippingMethod 與 shopToCountry 傳入 1
- domainId 對應參數 domain id
- 解析回傳 json 內容,判斷是否成功加入購物車,範例如下

- 主要判斷 code, message, messageIntl 回傳的內容
- 如果 code = 200 代表 service 正常處理,反之代表呼叫 service 發生錯誤
- message 紀錄本次呼叫相關顯示訊息
- messageIntl 紀錄 debug 相關訊息
- data 的部分主要紀錄與型錄相關的訊息,並不影響顯示
- 點選 Add to favorite
- 參考之前條列頁的加入最愛功能
- 加到我的最愛部分,這邊先貼上目前條列頁相同功能的程式碼,應該是可以直接套用的;條列頁程式有納入版本控制,後續開發如果碰到問題,這邊可以提供相關程式來參考;檢查型錄是否已經加入我的最愛、將型錄加入我的最愛、從我的最愛中移除型錄,在下面的 ts 中應該都有相對應的程式碼
- 相關連結 https://github.com/ttcloude/taitra-aem/blob/master/ui.frontend/src/app/library/product/product.component.ts
- 相關程式請參考下方程式碼區塊
- 我的最愛程式碼區塊
```=java
import { isPlatformServer } from '@angular/common';
import { Component, Inject, Input, PLATFORM_ID, ViewEncapsulation } from '@angular/core';
import { Currency } from 'src/app/models/currency.model';
import { ViewService } from 'src/app/service/view.service';
import { StatusService } from 'src/app/service/status.service';
import { AlertStore } from 'src/app/store/alert-store';
import { CurrencyStore } from 'src/app/store/currency-store';
import { BaseComponent } from '../../components/base.component';
import { ProductDisplay } from '../../models/product-display.model';
import { Product } from '../../models/product.model';
import { HttpService } from '../../service/http.service';
import { StringUtils } from '../../utils/string.utils';
import * as showModel from './product.js';
import { WINDOW } from '@ng-web-apis/common';
import { FavoriteProductStore } from '../../store/favorite-product-store';
import { ResetFavoriteListStore } from '../../store/reset-favorite-list-store';
import { ResetPermaCountStore } from '../../store/reset-perma-count-store';
@Component({
selector: 'app-product',
templateUrl: './product.component.html',
encapsulation: ViewEncapsulation.None
})
export class ProductComponent extends BaseComponent {
@Input()
productDisplay: ProductDisplay;
@Input()
product: Product;
@Input()
index: number;
@Input()
languageId: number;
@Input()
did: number;
@Input()
alert = true;
@Input()
searchResultFlag = false;
showProduct = false;
showBadge = false;
showBadgeRank = false;
showBadgeNew = false;
showBadgeTop = false;
showMedia = false;
showName = false;
showCertification = false;
showCompanyName = false;
showCompanyType = false;
showMinOrder = false;
showCfaImg = false;
showUnit = false;
showDiscount = false;
showOldPrice = false;
showPrice = false;
showOldTotalPrice = false;
showTotalPrice = false;
showIsFreeShipping = false;
mediaIconClass = '';
isCartProduct = false;
currency: Currency;
favoriteProductList: string[] = [];
isAddedFavorite = false;
resetFavoriteList = true;
constructor(
private httpService: HttpService,
currencyStore: CurrencyStore,
private favoriteProductStore: FavoriteProductStore,
private resetFavoriteListStore: ResetFavoriteListStore,
private resetPermaCountStore: ResetPermaCountStore,
private statusService: StatusService,
@Inject(ViewService) private viewService: ViewService,
private alertStore: AlertStore,
@Inject(PLATFORM_ID) private platformId: any,
@Inject(WINDOW) readonly windowRef: any) {
super();
this.log = false;
this.addStore(currencyStore, (currency) => {
this.currency = currency;
this.setPriceAndCurrency();
}, false);
this.addStore(favoriteProductStore, (favoriteList) => {
this.favoriteProductList = favoriteList;
this.resetFavoriteList = false;
if (this.product && this.favoriteProductList.includes(this.product.productId)) {
this.isAddedFavorite = true;
} else {
this.isAddedFavorite = false;
}
}, false);
}
onInit(): void {
// SSR忽略
if (isPlatformServer(this.platformId)) {
return;
}
this.initShowFlag();
if (this.product) {
this.showProduct = true;
if (this.alert) {
this.alertStore.addProduct(this.product);
} else {
this.debug('No alert for ' + this.product.productNameAlt);
}
if (this.resetFavoriteList) {
// Notify Header set favorit list
this.resetFavoriteListStore.resetFavoriteType('PRODUCT');
} else {
if (this.favoriteProductList.includes(this.product.productId)) {
this.isAddedFavorite = true;
} else {
this.isAddedFavorite = false;
}
}
// Show Discount: 前台顯示邏輯更正:若是「不可販售」,就不顯示「免運圖示」、「折扣」、minorder、金額
if (this.product.isSelling && StringUtils.isNotBlank(this.product.discount) && this.product.discount !== '0') {
this.showDiscount = true;
}
// Show Unit
if (StringUtils.isNotBlank(this.product.unit)) {
this.showUnit = true;
}
// Show Old Price: 前台顯示邏輯更正:若是「不可販售」,就不顯示「免運圖示」、「折扣」、minorder、金額
if (this.product.isSelling && this.showDiscount
&& StringUtils.isNotBlank(this.product.minOldPrice) && StringUtils.isNotBlank(this.product.maxOldPrice)) {
this.showOldPrice = true;
}
// Show Price: 前台顯示邏輯更正:若是「不可販售」,就不顯示「免運圖示」、「折扣」、minorder、金額
if (this.product.isSelling && StringUtils.isNotBlank(this.product.minPrice) && StringUtils.isNotBlank(this.product.maxPrice)) {
this.showPrice = true;
}
// Show Cart Product
if (StringUtils.isNotBlank(this.product.totalPrice) && this.product.quantity) {
this.isCartProduct = true;
}
// Show Old Total Price: 前台顯示邏輯更正:若是「不可販售」,就不顯示「免運圖示」、「折扣」、minorder、金額
if (this.product.isSelling && this.isCartProduct && StringUtils.isNotBlank(this.product.oldTotalPrice)) {
this.showOldTotalPrice = true;
}
// Show Total Price: 前台顯示邏輯更正:若是「不可販售」,就不顯示「免運圖示」、「折扣」、minorder、金額
if (this.product.isSelling && this.isCartProduct) {
this.showTotalPrice = true;
}
// Check Currency and Price
if (this.currency && this.currency.currency !== this.product.currency) {
this.setPriceAndCurrency();
}
if (this.productDisplay) {
this.checkCustomDisplayCondition();
}
}
}
checkCustomDisplayCondition(): void {
// Show Badge
if (StringUtils.isNotBlank(this.productDisplay.showBadge) && this.productDisplay.showBadge === 'rank') {
this.showBadge = true;
this.showBadgeRank = true;
} else if (StringUtils.isNotBlank(this.productDisplay.showBadge) && this.productDisplay.showBadge === 'new') {
this.showBadge = true;
this.showBadgeNew = true;
} else if (StringUtils.isNotBlank(this.productDisplay.showBadge) && this.productDisplay.showBadge === 'top') {
this.showBadge = true;
this.showBadgeTop = true;
}
// Show Media
if (this.productDisplay.showMedia && StringUtils.isNotBlank(this.product.mediaIcon)) {
this.showMedia = true;
this.mediaIconClass = this.product.mediaIcon;
}
// Show Name
if (this.productDisplay.showName) {
if (!this.searchResultFlag && StringUtils.isNotBlank(this.product.productNameAlt)) {
this.showName = true;
} else if (this.searchResultFlag && StringUtils.isNotBlank(this.product.productName)) {
this.showName = true;
}
}
// Show Certification
if (this.productDisplay.showCertification && StringUtils.isNotBlank(this.product.productCertification)) {
this.showCertification = true;
}
// Show CompanyName
if (this.productDisplay.showCompanyName && StringUtils.isNotBlank(this.product.companyNameAlt)) {
this.showCompanyName = true;
}
// Show CompanyType
if (this.productDisplay.showCompanyType && StringUtils.isNotBlank(this.product.companyType)) {
this.showCompanyType = true;
}
// Show MinOrder: 前台顯示邏輯更正:若是「不可販售」,就不顯示「免運圖示」、「折扣」、minorder、金額
if (this.product.isSelling && this.productDisplay.showMinOrder &&
StringUtils.isNotBlank(this.product.minOrder) &&
StringUtils.isNotBlank(this.product.unit)) {
this.showMinOrder = true;
}
// Show CfaImg
if (this.productDisplay.showCfaImg && this.product.cfaImg && this.product.cfaImg.length > 0) {
this.showCfaImg = true;
}
// Show IsFreeShipping: 前台顯示邏輯更正:若是「不可販售」,就不顯示「免運圖示」、「折扣」、minorder、金額
this.showIsFreeShipping = this.product.isSelling && this.productDisplay.showIsFreeShipping && this.product.isFreeShipping;
}
initShowFlag(): void {
this.showProduct = false;
this.showBadge = false;
this.showBadgeRank = false;
this.showBadgeNew = false;
this.showBadgeTop = false;
this.showMedia = false;
this.showName = false;
this.showCertification = false;
this.showCompanyName = false;
this.showCompanyType = false;
this.showMinOrder = false;
this.showCfaImg = false;
this.showUnit = false;
this.showDiscount = false;
this.showOldPrice = false;
this.showPrice = false;
this.showTotalPrice = false;
this.showIsFreeShipping = false;
this.mediaIconClass = '';
}
setPriceAndCurrency(): void {
const exchangeRate = this.currency.exchangeRate;
const currency = this.currency.currency;
if (this.product && this.product.currency !== currency) {
this.product.currency = currency;
this.product.minOldPrice = this.getPrice(this.product.minOldPrice, exchangeRate);
this.product.maxOldPrice = this.getPrice(this.product.maxOldPrice, exchangeRate);
this.product.minPrice = this.getPrice(this.product.minPrice, exchangeRate);
this.product.maxPrice = this.getPrice(this.product.maxPrice, exchangeRate);
this.product.oldTotalPrice = this.getPrice(this.product.oldTotalPrice, exchangeRate);
this.product.totalPrice = this.getPrice(this.product.totalPrice, exchangeRate);
}
}
getPrice(srcPrice: string, exchangeRate: number): string {
if (!StringUtils.isNumeric(exchangeRate)) {
return srcPrice;
}
if (StringUtils.isNumeric(srcPrice)) {
const price = parseFloat(srcPrice);
const result = price * exchangeRate;
return result.toFixed(2);
}
return srcPrice;
}
async addFavoriteClick(): Promise<void> {
this.debug('### click addFavoriteClick ###');
try {
this.statusService.mask();
if (this.isAddedFavorite) {
this.debug('### remove favorite:', this.product.productId);
const response = await this.windowRef.headerEntry.removeMyFavorites(this.product.productId, 'PRODUCT', this.languageId);
if (response.code === '200') {
this.debug('### remove success ###');
this.favoriteProductStore.removeProductId(this.product.productId);
}
} else {
this.debug('### add favorite:', this.product.productId);
const response = await this.windowRef.headerEntry.addMyFavorites(this.product.productId, 'PRODUCT', this.languageId, this.did, 'FRONT');
if (response.code === '200') {
this.debug('### add success ###');
this.favoriteProductStore.addProductId(this.product.productId);
}
}
this.resetPermaCountStore.updateType('PRODUCT');
} catch (e) {
this.error(e);
} finally {
this.statusService.unMask();
}
}
mediaIconClick(event: MouseEvent): void {
this.debug('### click media icon ###');
this.viewService.getProductPopup().refreshProduct(this.product, this.searchResultFlag);
showModel(event);
}
removeProductClick(): void {
this.debug('### click remove product ###');
}
}
```
#### tab 區塊,切換顯示

- 顯示邏輯
- 販售型錄的 tab 區塊分為五個對應 tab,預設顯示第一個
- 其中前兩個型錄詳目與公司詳目是固定一定會出現的;後三個與販售相關的區塊需要判斷 api 回傳的資料來決定是否要顯示
- Shipping & Packaging 請參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA#%E9%81%8B%E8%B2%BB%E8%B3%87%E6%96%99
- Payment Terms 請參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA#%E4%BB%98%E6%AC%BE%E5%96%AE%E4%BD%8D
- Return Policy 請參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA#%E7%9B%B8%E9%97%9C%E6%94%BF%E7%AD%96
- 取出 data 中的 shippingInfo object, paymentTermInfo array 與 returnPolicyInfo object
- 邏輯與格式
- 1 Product Detail
- 標題為多語,{lang.productdetail}
- 2 Company Profile
- 標題為多語,{lang.companyprofile}
- 3 Shipping & Packaging
- 標題為多語,{lang.ez_shippingpackaging}
- 如果 data 中的 shippingInfo object 非空,顯示此 tab
- 如果使用者有登入,此處需要傳遞相關使用者資訊到後台,這樣才能正確取得對應的 shippingCountryId
- 4 Payment Terms
- 標題為多語,{lang.paymentterms}
- 如果 data 中的 paymentTermInfo array 非空,顯示此 tab
- 5 Return Policy
- 標題為多語,{lang.ez_return_policy}
- 如果 data 中的 returnPolicyInfo object 非空,顯示此 tab
- 操作邏輯
- 透過 tab 去切換顯示對應的區塊
#### 詳細資料(Product Detail)

- 顯示邏輯
- 取出 data 中的 productInfo object, specInfo array, paymentInfo object, attachInfo array, certInfo array 來顯示資料
- 1 廣告詞
- {productInfo.adSlogan}
- 沒有的話不顯示此區塊
- 2 規格列表
- Spec 為多語,{lang.spec}
- 內容取自 specInfo array,依次顯示
- 每一行固定顯示格式為 {specInfo .name} + ":" + {specInfo.value}
- 沒有的話不顯示此區塊
- 3 Made in Taiwan 與 Design in Taiwan 的圖示
- 判斷 {productInfo.logoType}
- {productInfo.logoType} = 0 顯示 Design In Taiwan Logo
- {productInfo.logoType} = 1 顯示 Made In Taiwan Logo
- {productInfo.logoType} = -1 不顯示
- 沒有的話不顯示此區塊
- 4 Key Features
- Key Features 為多語,{lang.keyfeatures}
- {productInfo.keyFaturesHtml}
- 沒有的話不顯示此區塊
- 5 產品認證
- Product Certification 為多語,{lang.productcertification}
- 內容取自 certInfo array,依次顯示
- 顯示對應的認證圖,圖片 url 為 {certInfo.imgUrl},圖片 title 為 {certInfo.imgTitle},圖片 alt 為 {certInfo.imgAlt}
- 點選認證圖後會開啟燈箱顯示認證詳目資料
- 燈箱 url 為 {certInfo.lightboxUrl}
- 相關燈箱操作請參考下面的操作邏輯
- 7 附件列表
- Attachment 為多語,{lang.attachment}
- 內容取自 attachInfo array,依次顯示
- 以 url 方式顯示檔案連結
- 連結 href = {attachInfo.fileUrl}
- 連結名稱顯示檔案名稱與檔案大小,檔案名稱為 {attachInfo.fileName},檔案大小為 {attachInfo.fileSize}
- 沒有的話不顯示此區塊
- 8 影片內容
- Video 為多語,{lang.ez_video}
- 內容取自 {productInfo.videoUrl}, {productInfo.videoTitle}, {productInfo.videoTypeName}
- 影片類型 {productInfo.videoType} 啟動的類型只有 youtube 類型
- 沒有的話不顯示此區塊
- 9 最後更新時間
- Last Update 為多語,{lang.lastupdate}
- 內容顯示 {productInfo.updateDate}
- 操作邏輯
- 點選產品型錄認證燈箱
- 參考 [URI-C-02 型錄認證/公司認證燈箱](https://hackmd.io/OFJ68AIxTfePBEeKqlGa-w#%E7%87%88%E7%AE%B1%E5%85%83%E4%BB%B6) 取得對應 json 內容
- 傳入的 url 路徑 /certifications/prod/{certInfo.id}?lang={productInfo.languageId}&companyId={productInfo.supplierId}
- 解析回傳的 certInfo object 內容並顯示於燈箱中

- 1 燈箱名稱
- Product Certification 為多語,{lang.productcertification}
- 2 型錄認證名稱
- 顯示 {certInfo .name}
- 3 型錄認證圖片
- 顯示 {certInfo.imgUrl}
- 4 型錄認證編號
- Certification No 為多語,{lang.certificationno}
- 顯示 {certInfo.serial}
- 如果沒有就不顯示此欄位
- 5 型錄認證生效起始日期
- Certification Start Time 為多語,{lang.certificationstarttime}
- 顯示 {certInfo.startTime}
- 如果沒有就不顯示此欄位
- 6 型錄認證生效結束日期
- Certification End Time 為多語,{lang.certificationendtime}
- 顯示 {certInfo.endTime}
- 如果沒有就不顯示此欄位
- 7 型錄認證說明
- Certification description 為多語,{lang.certificationdesc}
- 顯示 {certInfo.desc}
- 如果沒有就不顯示此欄位
- 8 型錄認證附件檔案
- Attachments 為多語,{lang.certificationattachments}
- 顯示 url 連結
- href 顯示 {certInfo.fileUrl}
- 連結名稱顯示 {certInfo.fileName}
- 檔案大小顯示 {certInfo.fileSize}
- 如果沒有就不顯示此欄位
- 9 型錄認證相關型錄

- Product 為多語,{lang.product}
- 解析 certInfo.refProductRows array,依次顯示內容
- 9-1 顯示 {certInfo.refProductRows.imgUrl}
- 9-2 對應的型錄詳目頁 url 連結
- href 顯示 {certInfo.refProductRows.productCpUrl}
- 連結名稱顯示 {certInfo.refProductRows.productName}
- 如果沒有就不顯示此區塊
- 10 燈箱關閉按鈕
- 關閉燈箱按鈕,點選後關閉燈箱
- 11 燈箱遮罩
- 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕
#### 詳細資料(Company Profile)

- 顯示邏輯
- 參考文件 https://hackmd.io/WTAzi5tXQBuakQV1tkgATw?view#廠商資料
- ~~呼叫 url 參數為 /companies/detail/{did}/{id}?productId={productInfo.productId}&userId={loginUserId}~~
- ~~{loginUserId} 為登入人員的 userId,未登入時不需傳遞~~
- 呼叫 url 參數為 /companies/detail/{did}/{id}?productId={productInfo.productId}&userAccessTicket={userAccessTicke}
- 如果輸入參數 userAccessTicket 有值,代表使用者已登入,以參數方式傳給 companies detail api
- 主要分為 公司基本資料, 公司相關型錄, 公司工廠資料, 公司認證與產品認證, 公司得獎紀錄、大事記, 公司聯絡資訊 等區塊
- 取得 data 中的 companyProfile object
- 1 公司名稱
- ~~如果 {companyProfile.companyWebSiteUrl} != '',顯示為聯結的型態,名稱是 {companyProfile.companyName},聯結是 {companyProfile.companyWebSiteUrl}~~
- ~~如果 {companyProfile.companyWebSiteUrl} == '',單純顯示 {companyProfile.companyName} 即可~~
- 如果 {companyProfile.companyWebSiteUrl} != null,顯示為聯結的型態,名稱是 {companyProfile.companyName},聯結是 {companyProfile.companyWebSiteUrl}
- 如果 {companyProfile.companyWebSiteUrl} == null,且 {companyProfile.ecCompanyWebSiteUrl} != null,顯示為聯結的型態,名稱是 {companyProfile.companyName},聯結是 {companyProfile.ecCompanyWebSiteUrl}
- 如果 {companyProfile.companyWebSiteUrl} == null,且 {companyProfile.ecCompanyWebSiteUrl} == null,單純顯示 {companyProfile.companyName} 即可
- 2 Add to favorites
- Add to favorites 為多語,{lang.addtofavorites}
- 如果該公司已經加入過 favorites 的話,要顯示 Go to Favorites,{lang.gotofavorites}
- 點選的連結請參考條列頁的加到最愛與 redirect 到最愛的邏輯
- 加到我的最愛部分,這邊先貼上目前條列頁相同功能的程式碼,應該是可以直接套用的;條列頁程式有納入版本控制,後續開發如果碰到問題,這邊可以提供相關程式來參考;檢查公司是否已經加入我的最愛、將公司加入我的最愛、從我的最愛中移除公司,在下面的 ts 中應該都有相對應的程式碼
- 相關連結 https://github.com/ttcloude/taitra-aem/blob/master/ui.frontend/src/app/library/supplier-list/supplier-list.component.ts
- 程式內容請參考下方的條列頁我的最愛相關程式碼內容
- 3 Basic Information 為多語,{lang.basicinformation}
- 4 取得 data 中的 companyProfile object 解析資料,如果 companyProfile 沒有對應欄位,該行不顯示
- Company Name
- Company Name 為多語,{lang.companyname}
- 顯示 {companyProfile.companyName}
- Business Type
- Business Type 為多語,{lang.companiesbusinesstype}
- 顯示 {companyProfile.businessTypeListDesc}
- Year Established
- Year Established 為多語,{lang.yearestablished}
- 顯示 {companyProfile.yearEstablished}
- Factory Location
- Factory Location 為多語,{lang.factorylocation}
- 顯示 {companyProfile.factoryLocation}
- Total Revenue
- Total Revenue 為多語,{lang.totalrevenue}
- 顯示 {companyProfile.totalRevenue}
- Capitial
- Capitial 為多語,{lang.capitial}
- 先判斷 {companyProfile.showCapital} == 1 才會顯示資本額
- 顯示 {companyProfile.capital}
- No. of Employee
- No. of Employee 為多語,{lang.employee}
- 顯示 {companyProfile.employeeRank}
- Main Product
- Main Product 為多語,{lang.mainproducts}
- 顯示 {companyProfile.mainExportProductName}
- Main Export Market
- Main Export Market 為多語,{lang.mainexportmarket}
- 顯示 {companyProfile.mainExportMarketListDesc}
- 條列頁我的最愛相關程式碼內容
```=java
import { Component, ElementRef, Inject, Input, QueryList, ViewChild, ViewChildren, ViewEncapsulation } from '@angular/core';
import { WINDOW } from '@ng-web-apis/common';
import { Subscription } from 'rxjs';
import { Product } from '../../models/product.model';
import { BaseComponent } from '../../components/base.component';
import { ProductDisplay } from '../../models/product-display.model';
import { SearchCompany } from '../../models/search-company.model';
import { UiUtils } from 'src/app/utils/ui.utils';
import { FavoriteSupplierStore } from '../../store/favorite-supplier-store';
import { ResetFavoriteListStore } from '../../store/reset-favorite-list-store';
import { ResetPermaCountStore } from '../../store/reset-perma-count-store';
import { StatusService } from 'src/app/service/status.service';
import { initSwiper } from './supplierListSwiper';
import { destroy } from '../../components/baseSwiper';
@Component({
selector: 'app-supplier-list',
styleUrls: ['./supplier-list.component.css'],
templateUrl: './supplier-list.component.html',
encapsulation: ViewEncapsulation.None
})
export class SupplierListComponent extends BaseComponent {
@ViewChildren('appProducts', { read: ElementRef })
appProducts: QueryList<ElementRef>;
@ViewChild('viewAll', { read: ElementRef })
viewAllSilder: ElementRef;
@Input()
productDisplay: ProductDisplay;
@Input()
index: number;
@Input()
languageId: number;
@Input()
did: number;
subscription: Subscription;
searchCompany: SearchCompany;
supplierProducts: Product[];
showViewAll = false;
sliderTotalCount = 0;
sliderCount = 0;
isAddedFavorite = false;
favoriteSupplier: string[] = [];
resetFavoriteList = true;
constructor(
@Inject(WINDOW) readonly windowRef: any,
private favoriteSupplierStore: FavoriteSupplierStore,
private resetFavoriteListStore: ResetFavoriteListStore,
private resetPermaCountStore: ResetPermaCountStore,
private statusService: StatusService) {
super();
this.addStore(favoriteSupplierStore, (favoriteList) => {
this.favoriteSupplier = favoriteList;
this.resetFavoriteList = false;
if (this.searchCompany && this.favoriteSupplier.includes(this.searchCompany.companyId)) {
this.isAddedFavorite = true;
} else {
this.isAddedFavorite = false;
}
}, false);
}
async onRefresh() {
this.supplierProducts = [];
destroy(this.searchCompany?.id);
this.sliderCount = 0;
if (this.resetFavoriteList) {
// Notify Header set favorit list
this.resetFavoriteListStore.resetFavoriteType('SUPPLIER');
} else {
if (this.searchCompany && this.favoriteSupplier.includes(this.searchCompany.companyId)) {
this.isAddedFavorite = true;
} else {
this.isAddedFavorite = false;
}
}
}
@Input() set company(searchCompany: SearchCompany) {
this.searchCompany = searchCompany;
}
@Input() set products(products: Product[]) {
this.supplierProducts = products;
if (products) {
this.sliderTotalCount = products.length;
}
}
onDomChange(mutation: MutationRecord): void {
this.debug('onDomChange ' + mutation + ' , liElementCount=' + this.sliderCount);
if (!UiUtils.isSwiperSlideDivElement(mutation)) {
return;
}
this.sliderCount++;
this.debug('onDomChange sliderCount=' + this.sliderCount + ' , sliderTotalCount=' + this.sliderTotalCount);
if (this.sliderCount === this.sliderTotalCount) {
if (this.searchCompany.totalProducts > 4) {
this.showViewAll = true;
} else {
this.debug('onDomChange initSupplierProdsSwiperConfig');
initSwiper(this.searchCompany?.id);
}
} else if (this.sliderCount > this.sliderTotalCount) {
this.debug('onDomChange initSupplierProdsSwiperConfig');
initSwiper(this.searchCompany?.id);
}
}
getViewAllHeight(): number {
if (!this.showViewAll) {
return 0;
}
let viewAllHeight: number = this.viewAllSilder ? this.viewAllSilder.nativeElement.height : 0;
if (this.windowRef.innerWidth >= 768) {
if (this.appProducts) {
const appProduct: ElementRef = this.appProducts.first;
if (appProduct) {
const height = appProduct.nativeElement.getElementsByClassName('prod_img_wrapper')[0].getBoundingClientRect().height;
viewAllHeight = height;
}
}
}
this.debug('SupplierListComponent getViewAllHeight=' + viewAllHeight);
return viewAllHeight;
}
async addFavoriteClick(): Promise<void> {
this.debug('### click addFavoriteClick ###');
try {
this.statusService.mask();
const companyId = this.searchCompany.companyId;
if (this.isAddedFavorite) {
this.debug('### remove favorite:', companyId);
const response = await this.windowRef.headerEntry.removeMyFavorites(companyId, 'SUPPLIER', this.languageId);
if (response.code === '200') {
this.debug('### remove success ###');
this.favoriteSupplierStore.removeCompanyId(companyId);
}
} else {
this.debug('### add favorite:', companyId);
const response = await this.windowRef.headerEntry.addMyFavorites(companyId, 'SUPPLIER', this.languageId, this.did, 'FRONT');
if (response.code === '200') {
this.debug('### add success ###');
this.favoriteSupplierStore.addCompanyId(companyId);
}
}
this.resetPermaCountStore.updateType('SUPPLIER');
} catch (e) {
this.error(e);
} finally {
this.statusService.unMask();
}
}
}
```
##### 公司相關型錄

- 顯示邏輯
- 參考文件 https://hackmd.io/WTAzi5tXQBuakQV1tkgATw?view#%E7%9B%B8%E9%97%9C%E5%9E%8B%E9%8C%84
- 1 標題 Other Products 為多語,{lang.otherproducts}
- 取得 data 中的 productlist array,依次解析資料
- 2 圖片
- 顯示 {productlist.imageURL}
- 3 New tag
- 如果 {productlist.newest} == 1,需要顯示 New 標籤
- 4 型錄名稱聯結
- 名稱為 {productlist.productName},聯結為 {productlist.productUrl}
- 5 型錄價格
- 先判斷是否有販售,{productlist.isSelling} == 1,如果沒有販售就不用顯示價格
- 再來判斷是否有折扣
- 先判斷 {productlist.isDiscount} == 1(有折扣)
- 再判斷 {productlist.discountStartDate} 與 {productlist.discountEndDate} 跟今天日期的比較,判斷是否在折扣日內
- {productInfo.discountStartDate} 與 {productInfo.discountEndDate} 回傳的資料型態為 long,對應 linux timestamp,例如 1633332979000,代表台灣時間 2021-10-04 15:36:19
- 再判斷 {productlist.discount} 是否有值且 {productlist.discount} > 0.0
- 上面三點都成立,代表有折扣
- 如果沒有折扣,顯示 {productlist.priceCurrency} + {productlist.price} + {productlist.unit}
- 如果有折扣,顯示 {productlist.discount} + {productlist.priceCurrency} + ({productlist.price} * {productlist.discount}) + {productlist.unit}
- json 提供的是 USD 金額,如果頁面的幣別有替換,那顯示時需要將金額從 USD 換算為對硬幣別的金額,幣別顯示也要改為頁面的幣別
- 6 Free Shipping
- 判斷是否免運費,如果 {productlist.isFreeShipping} == 1,顯示 free shipping 的小圖
- 7 Min Order
- ~~顯示 {productlist.minOrder} + {productlist.unit}~~
- 顯示 {productlist.minOrder}
- 警語相關
- 取得 {productlist.catalogStandardCid},會影響到本頁是否要顯示警語
- 8 更多型錄圖示
- 判斷以下邏輯
- 在產品company profile下,型錄清單最多顯示 5 則,需判斷 {productCount} > 5
- ~~確定公司是否有 ep 網頁,{companyProfile.companyWebSiteUrl} != '' 代表是 ep~~
- ~~如果以上兩個條件都符合,顯示更多型錄~~
- ~~點選的聯結格式如下:{companyProfile.companyWebSiteUrl}/product-catalog/all-products.html~~
- 更多型錄圖示點選的 url = {productMoreUrl}
- 9 更多型錄文字
- 如果 8 有顯示,就要顯示對應的文字
- All Products 為多語,{lang.allproducts}
- All Products 後面的數量是 {productCount}
##### 公司工廠資料

- 顯示邏輯
- 參考文件 https://hackmd.io/WTAzi5tXQBuakQV1tkgATw?view#%E5%B7%A5%E5%BB%A0%E8%B3%87%E6%96%99
- 1 標題 Factory 為多語,{lang.factory}
- 取得 data 中的 factoryInfo.videoList array 與 factoryInfo.imageList array,依次解析資料
- 先解析 factoryInfo.videoList array,依次顯示
- 2 顯示影片,{factoryInfo.videoList.videoURL}
- 3 顯示影片名稱,{factoryInfo.videoList.videoTitle}
- 再來解析 factoryInfo.imageList array,依次顯示
- 4 顯示圖片,{factoryInfo.imageList.imageURL},圖片的 title 跟 alt 顯示 {factoryInfo.imageList.imageTitle}
- 5 顯示圖片名稱,{factoryInfo.imageList.imageTitle}
- 操作邏輯
- 點選工廠圖片,透過燈箱顯示圖片

- 1 點選的工廠圖片
- 2 開啟對應燈箱,顯示 {factoryInfo.imageList.imageURL}
- 3 燈箱關閉按鈕
- 關閉燈箱按鈕,點選後關閉燈箱
- 4 燈箱遮罩
- 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕
##### 公司認證與產品認證

- 顯示邏輯
- 1 Certification 為多語,{lang.certification}
- 取得 data 中的 certInfo.thirdPartyCert array, certInfo.companyCert array, certInfo.productCert array
- 2 解析第三方認證顯示邏輯(thirdPartyCert)
- 依次分析 {certInfo.thirdPartyCert} 資料
- 第三方認證的 tts 認證與 advanced 認證圖片是固定的路徑,因此回傳的 json 只記錄是否顯示,以及對應認證顯示的名稱,結構與其他認證不同
- 如果 {certInfo.ttsCert} == 1,代表要顯示 tts 的認證圖片,另外名稱顯示 certInfo.ttsCertName 的值,點選 tts 認證不會開啟燈箱
- 如果 certInfo.advancedCert == 1,代表要顯示 advanced 的認證圖片,另外名稱顯示 {certInfo.advancedCertName},點選 advanced 認證不會開啟燈箱
- 其他類型的認證,需要解析 {certInfo.thirdPartyCert.isUseHtml} 欄位值
- 如果 {certInfo.thirdPartyCert.isUseHtml} == 1,代表認證的內容要顯示 {certInfo.thirdPartyCert.informationHtml} 內的 html code
- 反之,依照一般認證顯示規則,點選後開啟燈箱,顯示相關內容,認證燈箱部分請參考 https://hackmd.io/OFJ68AIxTfePBEeKqlGa-w
- 3 Company Certification 為多語,{lang.companycertification}
- 4 解析 certInfo.companyCert 資料,依次顯示
- 如果 {certInfo.companyCert.isUseHtml} == 1,代表認證的內容要顯示 {certInfo.companyCert.informationHtml} 內的 html code
- 反之,依照一般認證顯示規則,點選後開啟燈箱,顯示相關內容,認證燈箱部分請參考 https://hackmd.io/OFJ68AIxTfePBEeKqlGa-w
- 5 解析以下條件,如果符合,顯示按鈕讓操作人員可以開啟視窗,並列出所有相關的公司認證
- 判斷數量 {certInfo.companyCert.total} > 0
- 判斷開啟的 url 非空,對應的 url 為{certInfo.companyCert.allCertHref} != ''
- 6 當第五項要顯示時,對應的名稱 All Certifications 為多語,{lang.allcertification}
- 顯示 All Certifications,後面加上 certInfo.companyCert.total 數量
- 7 Product Certification 為多語,{lang.productcertification}
- 8 解析 certInfo.productCert 資料,依次顯示
- 如果 {certInfo.productCert.isUseHtml} == 1,代表認證的內容要顯示 {certInfo.productCert.informationHtml} 內的 html code
- 反之,依照一般認證顯示規則,點選後開啟燈箱,顯示相關內容,認證燈箱部分請參考 https://hackmd.io/OFJ68AIxTfePBEeKqlGa-w
- 9 解析以下條件,如果符合,顯示按鈕讓操作人員可以開啟視窗,並列出所有相關的公司認證
- 判斷數量 {certInfo.productCert.total} > 0
- 判斷開啟的 url 非空,對應的 url 為{certInfo.productCert.allCertHref} != ''
- 10 當第九項要顯示時,對應的名稱 All Certifications 為多語,{lang.allcertification}
- 顯示 All Certifications,後面加上 {certInfo.productCert.total} 數量
- 操作邏輯
- 點選產品型錄認證燈箱
- 參考 [URI-C-02 型錄認證/公司認證燈箱](https://hackmd.io/OFJ68AIxTfePBEeKqlGa-w#%E7%87%88%E7%AE%B1%E5%85%83%E4%BB%B6) 取得對應 json 內容
- 傳入的 url 路徑 /certifications/prod/{certInfo.id}?lang={productInfo.languageId}&companyId={productInfo.supplierId}
- 解析回傳的 certInfo object 內容並顯示於燈箱中

- 1 燈箱名稱
- Product Certification 為多語,{lang.productcertification}
- 2 型錄認證名稱
- 顯示 {certInfo .name}
- 3 型錄認證圖片
- 顯示 {certInfo.imgUrl}
- 4 型錄認證編號
- Certification No 為多語,{lang.certificationno}
- 顯示 {certInfo.serial}
- 如果沒有就不顯示此欄位
- 5 型錄認證生效起始日期
- Certification Start Time 為多語,{lang.certificationstarttime}
- 顯示 {certInfo.startTime}
- 如果沒有就不顯示此欄位
- 6 型錄認證生效結束日期
- Certification End Time 為多語,{lang.certificationendtime}
- 顯示 {certInfo.endTime}
- 如果沒有就不顯示此欄位
- 7 型錄認證說明
- Certification description 為多語,{lang.certificationdesc}
- 顯示 {certInfo.desc}
- 如果沒有就不顯示此欄位
- 8 型錄認證附件檔案
- Attachments 為多語,{lang.certificationattachments}
- 顯示 url 連結
- href 顯示 {certInfo.fileUrl}
- 連結名稱顯示 {certInfo.fileName}
- 檔案大小顯示 {certInfo.fileSize}
- 如果沒有就不顯示此欄位
- 9 型錄認證相關型錄

- Product 為多語,{lang.product}
- 解析 certInfo.refProductRows array,依次顯示內容
- 9-1 顯示 {certInfo.refProductRows.imgUrl}
- 9-2 對應的型錄詳目頁 url 連結
- href 顯示 {certInfo.refProductRows.productCpUrl}
- 連結名稱顯示 {certInfo.refProductRows.productName}
- 如果沒有就不顯示此區塊
- 10 燈箱關閉按鈕
- 關閉燈箱按鈕,點選後關閉燈箱
- 11 燈箱遮罩
- 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕
- 點選公司型錄認證燈箱
- 參考 [URI-C-02 型錄認證/公司認證燈箱](https://hackmd.io/OFJ68AIxTfePBEeKqlGa-w#%E7%87%88%E7%AE%B1%E5%85%83%E4%BB%B6) 取得對應 json 內容
- 傳入的 url 路徑 /certifications/prod/{certInfo.id}?lang={productInfo.languageId}&companyId={productInfo.supplierId}
- 解析回傳的 certInfo object 內容並顯示於燈箱中

- 1 燈箱名稱
- Company Certification 為多語,{lang.companycertification}
- 2 公司名稱
- 顯示 {companyInfo.companyName}
- 3 認證名稱
- 顯示 {certInfo .name}
- 4 認證圖片
- 顯示 {certInfo.imgUrl}
- 5 認證相關顯示內容
- 認證編號
- Certification No 為多語,{lang.certificationno}
- 顯示 {certInfo.serial}
- 如果沒有就不顯示此欄位
- 認證生效起始日期
- Certification Start Time 為多語,{lang.certificationstarttime}
- 顯示 {certInfo.startTime}
- 如果沒有就不顯示此欄位
- 認證生效結束日期
- Certification End Time 為多語,{lang.certificationendtime}
- 顯示 {certInfo.endTime}
- 如果沒有就不顯示此欄位
- 認證說明
- Certification description 為多語,{lang.certificationdesc}
- 顯示 {certInfo.desc}
- 如果沒有就不顯示此欄位
- 認證附件檔案
- Attachments 為多語,{lang.certificationattachments}
- 顯示 url 連結
- href 顯示 {certInfo.fileUrl}
- 連結名稱顯示 {certInfo.fileName}
- 檔案大小顯示 {certInfo.fileSize}
- 如果沒有就不顯示此欄位
- 6 燈箱關閉按鈕
- 關閉燈箱按鈕,點選後關閉燈箱
- 7 燈箱遮罩
- 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕
##### 公司得獎紀錄

- 顯示邏輯
- 參考文件 https://hackmd.io/WTAzi5tXQBuakQV1tkgATw?view#%E5%BE%97%E7%8D%8E%E8%B3%87%E6%96%99
- 1 標題 Award 為多語,{lang.awards}
- 2 標題 Award from {companyProfile.companyName}
- Award 為多語,{lang.awards}
- from 為固定顯示
- 後面需要接上 {companyProfile.companyName}
- 取得 data 中的 awardInfo.awardList array,依次解析資料
- 3 顯示圖片,{awardInfo.awardList.imgSrc}
- 4 顯示年分,{awardInfo.awardList.startYear}
- 5 顯示得獎名稱與聯結,名稱為 {awardInfo.awardList.title},點選的聯結是 {awardInfo.awardList.href}
- 6 顯示說明,{awardInfo.awardList.description}
##### 公司大事記

- 顯示邏輯
- 參考文件 https://hackmd.io/WTAzi5tXQBuakQV1tkgATw?view#%E5%BE%97%E7%8D%8E%E8%B3%87%E6%96%99
- 1 標題 Official Records 為多語,{lang.officialrecords}
- 2 Official Records from Taiwan International Trade Shows 中的 Official Records 為多語,from Taiwan International Trade Shows 為固定顯示
- 取得 data 中的 awardInfo.officialRecordsFromTTSList array,依次解析資料
- 3 顯示圖片,{awardInfo.officialRecordsFromTTSList.logoUrl}
- 4 顯示年月,{awardInfo.officialRecordsFromTTSList.showYearMonth}
- 5 顯示名稱與聯結,名稱為 {awardInfo.officialRecordsFromTTSList.displayTitle},點選的聯結是 {awardInfo.officialRecordsFromTTSList.sUrl}
- 6 顯示序號,Booth No. 為固定顯示,後方序號為 {awardInfo.officialRecordsFromTTSList.showBoots}
- 7 Official Records from {公司名稱},Official Records 為多語,{lang.officialrecords},from 為固定顯示
- 後面需要接上 {companyProfile.companyName}
- 取得 data 中的 awardInfo.officialRecordList array,依次解析資料
- 8 顯示圖片,{awardInfo.officialRecordList.imgSrc}
- 9 顯示年分,{awardInfo.officialRecordList.startYear}
- 10 顯示名稱與聯結,名稱為 {awardInfo.officialRecordList.title},點選的聯結是 {awardInfo.officialRecordList.href}
- 11 顯示說明,{awardInfo.officialRecordList.description}
#### 公司聯絡資訊
- 未登入

- 已登入

- 顯示邏輯

- 參考文件 https://hackmd.io/WTAzi5tXQBuakQV1tkgATw?both#%E5%85%AC%E5%8F%B8%E8%81%AF%E7%B5%A1%E8%B3%87%E8%A8%8A
- 取得 data 中的 contactUs object
- 1 Contact Information 為多語,{lang.contactinformation}
- 2 聯絡人相關訊息
- 如果有登入,依次顯示資訊
- Owner 為多語,{lang.owner}
- 顯示 {contactUs.owner}
- Contact Person 為多語,{lang.contactperson}
- 顯示 {contactUs.contactFullName}
- 分析 {contactUs.contactPicture},如果有值,顯示聯絡人圖片
- 分析 {contactUs.contactCard},如果有值,顯示聯絡人名片圖
- Business Phone Number 為多語,{lang.tel1}
- 顯示 {contactUs.contactPhone}
- 如果沒有登入,owner 等資訊會是空的,此時請顯示 Log in to view details,多語資料 {lang.logintoviewdetails}
- Owner 為多語,{lang.owner}
- Contact Person 為多語,{lang.contactperson}
- Business Phone Number 為多語,{lang.tel1}
- 3 Contact Supplier 為多語,{lang.contactsupplier}
- 4 其他相關資訊
- 依次判斷以下 contactUs object 的欄位
- Company Fax Number 為多語,{lang.fax}
- 對應欄位 {contactUs.officeFax}
- Phone Number 為多語,{lang.phonenumber}
- 對應欄位 {contactUs.contactPhone}
- Office Address 為多語,{lang.officeaddress}
- 對應以下欄位
- {contactUs.officePostal}
- {contactUs.officeAddress}
- {contactUs.officeAddressTw}
- ~~如果 {contactUs.officeAddressTw} != '' 顯示 {contactUs.officePostal} + {contactUs.officeAddressTw}~~
- ~~如果 {contactUs.officeAddressTw} == '' and {contactUs.officeAddress} != '' 顯示 {contactUs.officePostal} + {contactUs.officeAddress}~~
- ~~後面加上 google map 的聯結,帶入對應的地址~~
- 欄位顯示 {contactUs.officePostal} + {contactUs.officeAddress}
- 後方的 google map 連結,其中的地址需要執行以下的判斷
- 如果 {contactUs.officeAddressTw} != null,此時 google map 的地址使用 {contactUs.officeAddressTw}
- 反之則不顯示 google map 按鈕
- Office Hours 為多語,{lang.officehours}
- 對應欄位 {contactUs.officeHour}
- Company Website 為多語,{lang.companywebsite}
- 顯示聯結
- 聯結名稱對應欄位 {companyProfile.companyName}
- ~~聯結 url 對應欄位 {companyProfile.companyWebSiteUrl}~~
- 連結 url 對應欄位 {contactUs.websiteUrl}
- Online Shop 為多語,{lang.onlineshop}
- 顯示聯結
- 聯結名稱對應欄位 {lang.onlineshop}
- 聯結 url {contactUs.onlineShop}
- Mobile Site 為多語,{lang.mobilesite}
- 顯示 qr code 圖,對應 url : {contactUs.qrcodeUrl}
- 5 Last Update 為多語,{lang.lastupdate}
- 操作邏輯
- 如果 2 聯絡人相關訊息 中有 聯絡人圖片,點選後會開啟燈箱顯示圖片

- 1 點選 聯絡人圖片 圖示
- 2 開啟燈箱內容,圖片部份顯示對應的 {contactUs.contactPicture}
- 3 目前顯示 avatar 單字
- 4 燈箱關閉按鈕
- 關閉燈箱按鈕,點選後關閉燈箱
- 5 燈箱遮罩
- 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕
- 如果 2 聯絡人相關訊息 中有 聯絡人名片,點選後會開啟燈箱顯示名片

- 1 點選 聯絡人名片 圖示
- 2 開啟燈箱內容,圖片部份顯示對應的 {contactUs.contactCard}
- 3 目前顯示 avatar 單字
- 4 燈箱關閉按鈕
- 關閉燈箱按鈕,點選後關閉燈箱
- 5 燈箱遮罩
- 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕
- 點選 3 Contact Supplier,會跳轉到本頁下面的 聯繫廠商 區塊
#### 聯繫廠商
- 商機Inquiry Product,請參考[URI-C-01](https://hackmd.io/Y-_iIT7rR4-uqwqN5ZGNXA?view)
#### 詳細資料(Shipping & Packaging)

- 顯示邏輯
- 參考 [URI-B-07 Shipping & Packaging API](https://hackmd.io/03JtG4EZT8aAqqnddSgWTQ?view#%E8%BC%B8%E5%87%BA%E5%8F%83%E6%95%B8%E8%AA%AA%E6%98%8E) 取得對應 json 內容
- ~~傳入的 url 路徑 /products/shipping/{did}/{id}/{countryId}~~
- ~~傳入的 url 路徑 /products/shipping/{did}/{id}/{countryId}?userId={loginUserId}~~
- 傳入的 url 路徑 /products/shipping/{did}/{id}/{countryId}?userAccessTicket={userAccessTicket}
- id 傳入 {productInfo.productId}
- countryId 一開始的預設值是 {shippingInfo.shippingCountryId}
- 之後如果使用者變更了國家(參考圖中的區塊 6),此時 countryId 傳入區塊 6 的值
- ~~如果使用者有登入,呼叫 api 時需要傳遞登入者的 loginUserId,此 userId 參數非必填,未登入就不需傳遞~~
- 如果使用者有登入,呼叫 api 時需要傳遞登入者的 userAccessTicket,此 useruserAccessTicketId 參數非必填,未登入就不需傳遞
- 解析回傳的 data object 內容
- 1 Calculate your shipping cost by countr/region and quantity 為多語,{lang.ez_calshippingcost}
- 2 Quantity 為多語,{lang.ez_quantity}
- 3 數量,輸入框,可輸入數字,預設為 1
- 4 Units 為多語,{lang.ez_unit}
- 5 Ship to 為多語,{lang.ez_shipto}
- 6 下拉選單,解析 data.shippingCountryList 的資料,依照順序顯示
- 下拉值:{data.shippingCountryList.code}
- 下拉名稱 : {data.shippingCountryList .name}
- 7 下拉區域,解析 data.shippingAreaList 的資料,依照順序顯示
- 下拉值:{data.shippingAreaList.code}
- 下拉名稱 : {data.shippingAreaList .name}
- 8 Zip Code 輸入框,目前舊版程式未特別處理
- 9 Shipping Company 為多語,{lang.ez_shippingcompany}
- 10 Shipping Cost 為多語,{lang.ez_shippingcost}
- 11 Estimated Delivery Time 為多語,{lang.ez_deliverytime}
- 12 價格列表
- 解析 data.shippingInfo.shippingDataList 的資料,依照順序顯示
- 第一欄顯示對應的圖片,依照 {data.shippingInfo.shippingDataList.deliveryCompanyCode} 來判斷顯示哪一張圖片
- [`DHL`](#頁面相關路徑)
- [`EMS`](#頁面相關路徑)
- [`FEDEX`](#頁面相關路徑)
- [`TNT`](#頁面相關路徑)
- [`UPS`](#頁面相關路徑)
- [`Other`](#頁面相關路徑)
- 第二欄顯示相關金額
- 先判斷 {data.shippingInfo.shippingDataList.shippingType} 值
- shippingType == 0 代表免運費,顯示 Free Shipping 圖片與 Free Shipping 多語 {lang.ez_freeshipping},[`Free Shipping`](#頁面相關路徑)
- shippingType == 1 代表自訂運費,透過以下欄位計算金額
- ~~{data.shippingInfo.shippingDataList.customPortage} 自訂運費需要有值~~
- ~~{data.shippingInfo.shippingDataList.customAmount} 自訂運費,每多 n 件多收的金額~~
- ~~{data.shippingInfo.shippingDataList.customOverAmount} 自訂運費,結合 customOverPortage 的應用,紀錄 每多 n 件的件數~~
- ~~顯示金額為 customAmount + customOverAmount * (欄位 3 的 Quantity)~~
- 自訂運費對應的欄位內容格式為 運送設置
{data.shippingInfo.shippingDataList.customAmount} 件之內,運費 {data.shippingInfo.shippingDataList.customPortage}。每超過 {data.shippingInfo.shippingDataList.customOverAmount} 件,增加運費 {data.shippingInfo.shippingDataList.customOverPortage}。
- 計算方式如下(以下的 quantity 代表欄位三的值):
- 如果運送數量未達加收運費的件數(quantity <= {data.shippingInfo.shippingDataList.customAmount}),此時運費 = {data.shippingInfo.shippingDataList.customPortage}
- 運送數量達到加收運費的狀況(quantity > {data.shippingInfo.shippingDataList.customAmount}),此時先計算加收運費單位(overFee),這只是一個暫存的參數,後面的程式計算會用到;首先檢查 "每超過 {data.shippingInfo.shippingDataList.customOverAmount} 件,增加運費 {data.shippingInfo.shippingDataList.customOverPortage}" 中的 {data.shippingInfo.shippingDataList.customOverAmount} 與 {data.shippingInfo.shippingDataList.customOverPortage} 是否都有值
- 都有值,代表加收運費正常,overFee = (quantity - 不需加收的件數) / (每超過 n 件增加運費),得到的結果向上取整數,代表有多少單位需要加收運費
- 值有缺,此時 overFee = 0
- 運費 = 運費 + (加收運費件數 * 多收的單位運費)
- 程式範例如下:
var fee = 0; // 最後的運費
if (quantity <= customAmount) {
fee = customPortage;
} else {
var overFee = 0;
if (customOverAmount && customOverAmount > 0) {
overFee = Math.ceil((quantity - customAmount) / customOverAmount);
}
fee = customPortage + overFee * customOverPortage;
}
- shippingType == 2 代表固定運費,固定顯示 {data.shippingInfo.shippingDataList.fixFee}
- shippingType == 3 代表 UPS 優惠運費,固定顯示 {data.shippingInfo.shippingDataList.monetaryValue} * quantity;如果回傳 null 或是 0,也是代表免運費的意思
- 第三欄顯示運送時間
- 如果有 durationStart 跟 durationEnd
- 顯示 {{ durationStart }} - {{ durationEnd }} days,多語 {lang.days}
- 如果只有 durationStart
- 顯示 {{ durationStart }} days,多語 {lang.days}
- 如果兩者都沒有
- 顯示 Expected delivery time contact suppliers,多語 {lang.ez_expecteddeliverytime}
- 操作邏輯
- 修改 Quantity 時,如果是自訂運費,要重新顯示對應的金額
- 參考上方 shippingType == 2 的情況
- 下拉選單修改 Country 的值,對應區塊 6
- 重新呼叫 api,將回傳結果解析並放在畫面上
#### 詳細資料(Payment Terms)

- 顯示邏輯
- 參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA#%E4%BB%98%E6%AC%BE%E5%96%AE%E4%BD%8D
- 取得 data 中的 paymentTermInfo array
- 邏輯與格式
- 1 Accepted payment method 為多語,{lang.ez_acceptedpaymentmethod}
- 2 Currency 為多語,{lang.ez_currency}
- 3 依照 {paymentTermInfo.code} 決定顯示什麼圖
- [`Paypal`](#頁面相關路徑)
- [`支付寶 alipay`](#頁面相關路徑)
- [`信用卡 card`](#頁面相關路徑)
- [`財付通 tenpay`](#頁面相關路徑)
- [`銀聯卡 unionpay`](#頁面相關路徑)
- [`網際威信 hitrust`](#頁面相關路徑)
- [`信用卡(AE) card_ae`](#頁面相關路徑)
- [`google pay`](#頁面相關路徑)
- [`apple pay`](#頁面相關路徑)
- 4 顯示 {paymentTermInfo.currency}
#### 詳細資料(Return Policy)

- 顯示邏輯
- 參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA#%E7%9B%B8%E9%97%9C%E6%94%BF%E7%AD%96
- 取得 data 中的 returnPolicyInfo object
- 邏輯與格式
- 顯示 {returnPolicyInfo.htmlContent}
#### 聯繫廠商
- 商機Inquiry Product,請參考[B-06C](https://hackmd.io/FDa_t2QwRxKoBimH6mW4qQ?view)
#### 推薦區塊

- 顯示邏輯
- 參考 [URI-B-05 詳細頁 Cp Recommend API](https://hackmd.io/ogBAYWnFQeGUTgYPo9aZPg?both) 取得對應 json 內容
- 取得 Recommends 方式如下
- 傳入的 url 路徑 /products/detail/recommends/{did}?id={pid}&type=product
- did 傳入進入本 cp 頁一開始的 did
- pid 傳入 {productInfo.productId}
- 解析 json 內容,取得 products array 中的內容,依次顯示
- 1 Recommendations 為多語,{lang.recommendation}
- 2 顯示型錄圖片,{products.productPicture100}
- 3 顯示型錄名稱,{products.productNameAlt},點選對應的 cp url 為 {products.productUrl}
- 警語相關
- 取得 {products.catalogStandardCid},會影響到本頁是否要顯示警語
- 取得 Small Order Recommendation 方式如下
- 傳入的 url 路徑 /products/detail/small-orders/{did}?id={pid}&type=product
- did 傳入進入本 cp 頁一開始的 did
- pid 傳入 productInfo.productId
- 解析 json 內容,取得 products array 中的內容,依次顯示
- 4 Small Order Recommendation 為多語,{lang.ez_smallrecommendation}
- 5 顯示型錄圖片,{products.productPicture100}
- 6 顯示型錄名稱,{products.productNameAlt},點選對應的 cp url 為 {products.productUrl}
- 7 顯示型錄銷售金額,small order 必定是販售型錄
- 先判斷 {products.discount} 是否有值且 {products.discount} > 0.0,符合代表有折扣
- 如果沒有折扣,金額顯示原價;如果有折扣,金額顯示折扣價
- 折扣顯示金額請用 ({products.discount} * {products.minPrice}) + '-' + ({products.discount} * {products.maxPrice})
- 後方加上 {products.unit}
- json 提供的是 USD 金額,如果頁面的幣別有替換,那顯示時需要將金額從 USD 換算為對硬幣別的金額,幣別顯示也要改為頁面的幣別
- 8 判斷顯示 free shipping 小圖與最小訂購量
- 如果第 7 點判斷有折扣,需要顯示折扣資訊
- 如果 {products.isFreeShipping} == 1,代表需要顯示免運費圖示
- 如果 {products.minOrder} != 0,顯示最小訂購量,{products.minOrder} + {products.unit}
- 警語相關
- 取得 {products.catalogStandardCid},會影響到本頁是否要顯示警語
- 操作邏輯
- 點選型錄圖片與聯結時,將會 redirect 到該型錄的 cp 頁
#### 警語相關邏輯
- 目前警語顯示規則如下:
- 先透過 url 取得相關警語顯示的規則與顯示內容,範例 url 如下
- https://aem-apollo.taiwantrade.com/graphql?operationName=Warnings&variables=%7B%22did%22:2%7D&extensions=%7B%22persistedQuery%22:%7B%22version%22:1,%22sha256Hash%22:%22408620505421b126559cb52bcb72391e669697d6202176bdc261d6cd9e69143e%22%7D%7D
- 其中 variable 中所傳遞的 did 對應目前頁面的 did
- 取得的內容結構如下,html code 的警語範例如下,上圖是 url 實際畫面,下圖是結構化後的樣式
- 
- 
- 圖片結構化後的樣式如下
- 
- 分析取得的警語相關檔案的結構
- data.warnings 是 array 結構,紀錄所有要顯示警語的規則,每一個條件都要判斷,所以一個頁面可能會顯示多項警語
- data.warnings.displayMode 代表格式
- displayMode = 1 為 使用圖片
- displayMode = 2 為 使用 html code
- data.warnings.cidList 代表標準 taitra code 條件
- cidList 是一個以逗號分隔的 string,例如 "cidList" : "543,123,456"
- 本文件中有提到警語顯示相關的部分有 [型錄主要資訊](#%E5%9E%8B%E9%8C%84%E4%B8%BB%E8%A6%81%E8%B3%87%E8%A8%8A), [公司相關型錄](#%E5%85%AC%E5%8F%B8%E7%9B%B8%E9%97%9C%E5%9E%8B%E9%8C%84), [推薦區塊](#%E6%8E%A8%E8%96%A6%E5%8D%80%E5%A1%8A)
- 如果這些區塊中的 catalogStandardCid 值存在於 cidList 中,代表對應的型錄符合這一項警語的規則,所以要顯示此區塊的警語
- 以 "cidList" : "543,123,456" 為例,如果本頁面中的任何一個區塊的 catalogStandardCid = 543 or catalogStandardCid = 123 or catalogStandardCid = 456,都要顯示這一項 data.warnings 的警語
- data.warnings.dPictureUrl, data.warnings.mPictureUrl
- data.warnings.displayMode == 1,代表使用圖片,警語直接顯示對應的圖片
- data.warnings.dPictureUrl 為大圖 url,用在一般頁面
- data.warnings.mPictureUrl 為小圖 url,用在行動版頁面
- data.warnings.dPictureHeight, data.warnings.mPictureHeight
- data.warnings.displayMode == 1,代表使用圖片,警語直接顯示對應的圖片
- 由於一次可能要顯示多張警語圖片,未來套版可能需要先計算出預計的圖片高度,可以透過這兩個欄位取得一般頁面與行動版頁面的圖片高度
- 資料格式為 number
- data.warnings.htmlCode
- data.warnings.displayMode == 2,代表使用 html code
- 警語顯示一段 html 內容,顯示的 html code = {data.warnings.htmlCode}
- data.warnings.backgroundColor
- data.warnings.displayMode == 2,代表使用 html code
- 如果有設定內容,代表警語的 html 區塊的背景顏色
- 例如上圖範例,data.warnings.backgroundColor = '#080404',代表 html code 的背景顏色會是 黑色
## 多語清單
區塊 | 多語代碼 | 說明
-------------|--------------------------------|------------------------------
麵包屑 | lang.home | 麵包屑 Home
圖片與多媒體 | lang.ez_enlarge | enlarge 按鈕名稱
型錄主要資訊 | lang.ez_daysleft | 折扣剩餘天數
型錄主要資訊 | lang.modelno | 型錄型號
型錄主要資訊 | lang.madein | 型錄產地
型錄主要資訊 | lang.supplier | 廠商名稱
型錄主要資訊 | lang.ez_moreaboutproduct | More About This Product
型錄主要資訊 | lang.ez_unitprice | Unit Price
型錄主要資訊 | lang.ez_discountprice | Discount Price
型錄主要資訊 | lang.price | Price
型錄主要資訊 | lang.ez_quantity | Quantity
型錄主要資訊 | lang.ez_processingtime | Processing Time
型錄主要資訊 | lang.ez_days | Days
型錄主要資訊 | lang.requestforquotation | Request For Quotation
型錄主要資訊 | lang.ez_pleaseselect | Please Select
型錄主要資訊 | lang.ez_available | Available
型錄主要資訊 | lang.ez_totalprice | Total Price
型錄主要資訊 | lang.more | More
型錄主要資訊 | lang.ez_minorder | Min Order.
型錄主要資訊 | lang.ez_buynow | Buy Now
型錄主要資訊 | lang.contactsupplier | Contact Supplier
型錄主要資訊 | lang.ez_addtocart | Add to Cart
型錄主要資訊 | lang.addtofavorites | Add to favorites
型錄主要資訊 | lang.gotofavorites | Go to favorites
tab 區塊 | lang.productdetail | Product Detail
tab 區塊 | lang.companyprofile | Company Profile
tab 區塊 | lang.ez_shippingpackaging | Shipping & Packaging
tab 區塊 | lang.paymentterms | Payment Terms
tab 區塊 | lang.ez_return_policy | Return Policy
型錄 Detail | lang.spec | Spec
型錄 Detail | lang.keyfeatures | Key Features
型錄 Detail | lang.productcertification | Product Certification
型錄 Detail | lang.paymentdetails | Payment Details for Offline Orders
型錄 Detail | lang.paymentterms | Payment Terms
型錄 Detail | lang.minimumorde | Minium Order
型錄 Detail | lang.attachment | Attachment
型錄 Detail | lang.ez_video | Video
型錄 Detail | lang.lastupdate | Last Update
型錄認證燈箱 | lang.productcertification | Product Certification
型錄認證燈箱 | lang.certificationno | Certification No
型錄認證燈箱 | lang.certificationstarttime | Certification Start Time
型錄認證燈箱 | lang.certificationendtime | Certification End Time
型錄認證燈箱 | lang.certificationdesc | Certification Description
型錄認證燈箱 | lang.certificationattachments | Attachments
型錄認證燈箱 | lang.product | Product
公司基本資料 | lang.addtofavorites | Add to favorite
公司基本資料 | lang.gotofavorites | Go to favorite
公司基本資料 | lang.basicinformation | Basic Information
公司基本資料 | lang.companyname | Company Name
公司基本資料 | lang.companiesbusinesstype | Business Type
公司基本資料 | lang.yearestablished | Year Establish
公司基本資料 | lang.factorylocation | Factory Location
公司基本資料 | lang.totalrevenue | Total Revenue
公司基本資料 | lang.capitial | Capital
公司基本資料 | lang.employee | Employee No.
公司基本資料 | lang.mainproducts | Main Products
公司基本資料 | lang.mainexportmarket | Main Export Market
公司相關型錄 | lang.otherproducts | Other Products
公司相關型錄 | lang.allproducts | All Products
公司工廠資料 | lang.factory | Factory
認證 | lang.certification | Certification
認證 | lang.companycertification | Company Certification
認證 | lang.allcertification | All Certification
認證 | lang.productcertification | Product Certification
認證燈箱 | lang.certificationno | Certification No
認證燈箱 | lang.certificationstarttime | Certification Start Time
認證燈箱 | lang.certificationendtime | Certification End Time
認證燈箱 | lang.certificationdesc | Certification Description
認證燈箱 | lang.certificationattachments | Attachments
得獎紀錄 | lang.awards | Awards
得獎紀錄 | lang.officialrecords | Official Records
Contact Us | lang.logintoviewdetails | Login in to View Details
Contact Us | lang.contactinformation | Contact Information
Contact Us | lang.owner | Owner
Contact Us | lang.contactperson | Contact Person
Contact Us | lang.tel1 | Contact Phone No.
Contact Us | lang.contactsupplier | Contact Supplier
Contact Us | lang.fax | Office Fax
Contact Us | lang.phonenumber | Office Phone No.
Contact Us | lang.officeaddress | Office Address
Contact Us | lang.officehours | Office Hours
Contact Us | lang.companywebsite | Company Website
Contact Us | lang.onlineshop | Online Shop
Contact Us | lang.mobilesite | Mobile Site
Contact Us | lang.lastupdate | Last Update
運費區塊 | lang.product | Product
運費區塊 | lang.ez_calshippingcost | Calculate your shipping cost by countr/region
運費區塊 | lang.ez_quantity | Quantity
運費區塊 | lang.ez_unit | Units
運費區塊 | lang.ez_shipto | Ship to
運費區塊 | lang.ez_shippingcompany | Shipping Company
運費區塊 | lang.ez_shippingcost | Shipping Cost
運費區塊 | lang.ez_deliverytime | Estimated Delivery Time
運費區塊 | lang.ez_freeshipping | Free Shipping
運費區塊 | lang.days | Days
運費區塊 | lang.ez_expecteddeliverytime | Expected delivery time contact suppliers
payment term | lang.ez_acceptedpaymentmethod | Accepted payment method
payment term | lang.ez_currency | Currency
推薦型錄 | lang.recommendation | Recommendation
推薦型錄 | lang.ez_smallrecommendation | Small Order Recommendation