# B-5 非販售產品詳細頁 > [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-C-02](https://hackmd.io/OFJ68AIxTfePBEeKqlGa-w) | 取得燈箱相關資訊 | 3 | 點選型錄認證/公司認證開啟燈箱 | 無 7 | [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 ## 原頁面網址 - http://www.ttstaging.com.tw/product/1895999 ## 頁面輸入 URL 解析 - 進入頁面方式有兩種 - /product/{id} - 範例 : http://www.ttstaging.com.tw/product/1895999 - id : 型錄 id,為純數字 - /product/{safe-name}-{id}.html - 範例 : https://www.ttstaging.com.tw/product/%E6%9C%AA%E8%B2%A9%E5%94%AE%E5%9E%8B%E9%8C%84-1895999.html - safe-name : 型錄名稱,為純文字,且型錄名稱會將英文轉為小寫,空白、特殊符號等字元替換為 - 號,例如 型錄名稱 為 **TDK Blu Ray DL 4X 50GB** 將會轉為 **tdk-blu-ray-dl-4x-50gb** - id : 型錄 id,為純數字 - id 為主要參數,會應用在後面的 api 中 ## 頁面說明 ![](https://i.imgur.com/1meSpHR.jpg) ![](https://imgur.com/HybBT3I.jpg) - 型錄詳目頁來源主要透過[URI-B-04](https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA)取得相關資料 - api 相關 url 如下:${TT-API網址}/products/detail/{did}/{id}?userAccessTicket={userAccessTicket} - did : domain id,與頁面及版型相關參數,例如英文主頁 did = 2 - id : 公司 id,參考 http://www.ttstaging.com.tw/company/65 ,其中 "65" 就是公司 id - userAccessTicket 為登入者使用者Ticket,AEM 透過 header service 取得 userAccessTicket,如果有傳入,且經過解析後是正常的 ticket,後續 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,顯示相關畫面 - 推薦型錄 ### 頁面欄位說明 #### SeoMeta 資訊 - 舊版頁面相關資訊透過 xml 傳遞 ![](https://i.imgur.com/nd2g6gV.jpg) - 產出 html 頁面時,需要將相關資訊放入 meta tag 中 ![](https://i.imgur.com/Wt1DmGK.jpg) - ~~顯示邏輯~~ - ~~參考 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://i.imgur.com/yLAZQfN.jpg) - 顯示邏輯 - 參考 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://i.imgur.com/PmmFQZS.jpg) - 顯示邏輯 - 圖片部分參考 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 : [`沒有圖片時的小圖`](#頁面相關路徑) ![](https://i.imgur.com/Ijvi1Tf.jpg) - 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 ![](https://i.imgur.com/MDzy6hB.png) - 操作邏輯 - 點選 多媒體3D 或 多媒體360 圖示,顯示相關操作燈箱 ![](https://i.imgur.com/D2yRXkr.jpg) - 帶入目前 multimediaInfo 內的資料 - 1 透過 iframe,將對應的 html 放入燈箱內 - 點選 3D 時,iframe 顯示 {multimediaInfo.multimedia_3d_url} - 點選 360 時,iframe 顯示 {multimediaInfo.multimedia_360_url} - 2 關閉燈箱按鈕,點選後關閉燈箱 - 3 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕 - 點選 多媒體 Video 圖示,顯示相關操作燈箱 ![](https://i.imgur.com/m2WzPOW.jpg) - 帶入目前 multimediaInfo 內的資料 - 1 透過 iframe,將對應的影片放入燈箱內 - 如果影片是 youtube,可使用 youtube - 如果不是,直接將對應內容放入 iframe - 2 關閉燈箱按鈕,點選後關閉燈箱 - 3 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕 - 當關閉燈箱時,如果影片正在撥放,此時需要停止撥放影片 - 目前舊版程式對 youtube 影片有做此處理,關閉燈箱時會呼叫 youtube 的 stopVideo 來停止撥放 - 點選 Enlarge,顯示相關操作燈箱 ![](https://i.imgur.com/ptCeR8F.jpg) - 帶入目前 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 的效果 ![](https://i.imgur.com/pukHhVx.jpg) - 首先判斷當前瀏覽器頁面的寬度是否 > 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 手動捲動效果 ![](https://i.imgur.com/WNORrEW.jpg) - slider 超過兩則圖片時,需要提供 slider 效果 - 舊版採用 slick slide 套件,載入 slick-utils.js - 點選向左或是向右,型錄小圖 slider 會捲動到前一筆或是後一筆 - 捲動的同時,上方型錄大圖也會跟著切換為當前第一張小圖對應的大圖 - 點選小圖,會將上面的大圖換為點選對應的大圖 - 點選下方小圖,會將上面的大圖換為點選對應的大圖 - 點選後,小圖 slider 的 index 會切換到目前點選的項目 #### 型錄主要資訊 ![](https://i.imgur.com/qTkt7EN.jpg) - 顯示邏輯 - 型錄相關內容參考 https://hackmd.io/nlZlemkdTRCC7ua4Uup8UA#%E5%9E%8B%E9%8C%84%E8%B3%87%E6%96%99 - 邏輯與格式 - 1 型錄標題 - {productInfo.productName} - 2 型錄型號 - 標題為多語,{lang.modelno} - {productInfo.modelNo} - 沒有資料時不顯示本欄 - 3 產地 - 標題為多語,{lang.madein} - {productInfo.madeIn} - 沒有資料時不顯示本欄 - 4 廠商資訊 ![](https://i.imgur.com/lTPVFgB.jpg) - 4-1 標題為多語,{lang.supplier} - 4-2 顯示廠商名稱,{productInfo.supplierName} - ~~4-2 檢查 {productInfo.epUrl} 是否存在,如果存在,廠商名稱加上 url 連結,連結的 url = {productInfo.epUrl}~~ - 檢查 {productInfo.supplierUrl} != '',有值代表該廠商相關公司網址,此時廠商名稱加上 url 連結,連結的 url = {productInfo.supplierUrl} - ~~4-3 檢查 {productInfo.epUrl} 是否存在,如果存在才顯示 More About This Product 項目,點選的 url = {productInfo.epUrl} + '/product/' + {productInfo.productId}~~ - 4-3 檢查 {productInfo.epUrl} 是否存在,如果存在才顯示 More About This Product 項目,點選的 url = {productInfo.epUrl} - 4-3 More About This Product 為多語,{lang.ez_moreaboutproduct} - 目前 api 會額外回傳以下相關資料 - 公司 ep 年資:{productInfo.supplierEpYear} - 公司認證小圖清單:{productInfo.supplierIcon} - 公司營業類型:{productInfo.supplierBusinessType} - 5 顯示 Request for Quotation 連結 - 連結名稱為多語,{lang.requestforquotation} - 連結網址固定,格式為 "/latest-price/step1?productId=" + {productInfo.productId} - 6 Contact Supplier 按鈕 - Contact Supplier 為多語,{lang.contactsupplier} - 點選相關邏輯請參考下面的操作邏輯 - 7 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} == 0 代表非販售,進入非販售產品詳細頁 - 操作邏輯 - 點選 Contact Supplier 按鈕 - 跳轉到下面的 "聯繫廠商" 區塊 - 點選 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 區塊,切換顯示 ![](https://i.imgur.com/ZTLVaWd.jpg) - 顯示邏輯 - 未販售型錄的兩個 tab 是型錄詳目與公司詳目 - 1 Product Detail - 標題為多語,{lang.productdetail} - 2 Company Profile - 標題為多語,{lang.companyprofile} - 操作邏輯 - 透過 tab 去切換顯示對應的區塊 #### 詳細資料(Product Detail) ![](https://i.imgur.com/PEYi1T1.jpg) - 顯示邏輯 - ~~取出 data 中的 productInfo object, specInfo array, paymentInfo object, attachInfo array, certInfo array 來顯示資料~~ - 取出 data 中的 productInfo object, specInfo array, 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 內容並顯示於燈箱中 ![](https://i.imgur.com/id1t7B0.jpg) - 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 型錄認證相關型錄 ![](https://i.imgur.com/QF3j6lT.jpg) - 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://i.imgur.com/SCjxDmI.jpg) - 顯示邏輯 - 參考 [URI-B-04](https://hackmd.io/WTAzi5tXQBuakQV1tkgATw?view#%E8%A6%8F%E6%A0%BC1) 取得對應 json 內容 - ~~呼叫 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 - 主要分為 公司基本資料, 公司相關型錄, 公司工廠資料, 公司認證與產品認證, 公司得獎紀錄、大事記, 公司聯絡資訊 等區塊 ##### 公司基本資料 ![](https://i.imgur.com/vkEFbOP.jpg) - 顯示邏輯 - 參考文件 https://hackmd.io/WTAzi5tXQBuakQV1tkgATw?view#廠商資料 - 取得 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://i.imgur.com/FukSQAh.jpg) - 顯示邏輯 - 參考文件 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} 跟今天日期的比較,判斷是否在折扣日內 - 再判斷 {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 更多型錄圖示 - 判斷以下邏輯 - 目前型錄清單最多顯示 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://i.imgur.com/Gn2G6o3.jpg) - 顯示邏輯 - 參考文件 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} - 操作邏輯 - 點選工廠圖片,透過燈箱顯示圖片 ![](https://i.imgur.com/snPARDi.jpg) - 1 點選的工廠圖片 - 2 開啟對應燈箱,顯示 {factoryInfo.imageList.imageURL} - 3 燈箱關閉按鈕 - 關閉燈箱按鈕,點選後關閉燈箱 - 4 燈箱遮罩 - 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕 ##### 公司認證與產品認證 ![](https://i.imgur.com/j5jmpkZ.jpg) - 顯示邏輯 - 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 內容並顯示於燈箱中 ![](https://i.imgur.com/id1t7B0.jpg) - 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 型錄認證相關型錄 ![](https://i.imgur.com/QF3j6lT.jpg) - 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 內容並顯示於燈箱中 ![](https://i.imgur.com/sFASn2z.jpg) - 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://i.imgur.com/LuuSZjm.jpg) - 顯示邏輯 - 參考文件 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://i.imgur.com/Jf0B3SJ.jpg) - 顯示邏輯 - 參考文件 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://i.imgur.com/5JhiZMQ.jpg) - 已登入 ![](https://i.imgur.com/zCuJJIt.jpg) - 顯示邏輯 ![](https://i.imgur.com/gfmtFal.jpg) - 參考文件 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} - Last Update 資料請參考 {contactUs.lastUpdateTime},該欄位回傳的日期是 long 格式的數字,套版時可以將數字轉換為對應時區的字串 - 操作邏輯 - 如果 2 聯絡人相關訊息 中有 聯絡人圖片,點選後會開啟燈箱顯示圖片 ![](https://i.imgur.com/53BkE8E.jpg) - 1 點選 聯絡人圖片 圖示 - 2 開啟燈箱內容,圖片部份顯示對應的 {contactUs.contactPicture} - 3 目前顯示 avatar 單字 - 4 燈箱關閉按鈕 - 關閉燈箱按鈕,點選後關閉燈箱 - 5 燈箱遮罩 - 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕 - 如果 2 聯絡人相關訊息 中有 聯絡人名片,點選後會開啟燈箱顯示名片 ![](https://i.imgur.com/LhMzWed.jpg) - 1 點選 聯絡人名片 圖示 - 2 開啟燈箱內容,圖片部份顯示對應的 {contactUs.contactCard} - 3 目前顯示 avatar 單字 - 4 燈箱關閉按鈕 - 關閉燈箱按鈕,點選後關閉燈箱 - 5 燈箱遮罩 - 燈箱背景會加上 mask,點選 mask 效果等同於點選關閉燈箱按鈕 - 點選 3 Contact Supplier,會跳轉到本頁下面的 聯繫廠商 區塊 #### 聯繫廠商 - 商機Inquiry Product,請參考[URI-K-32](https://hackmd.io/Y-_iIT7rR4-uqwqN5ZGNXA?view) #### 推薦區塊 ![](https://i.imgur.com/cSNKeCe.jpg) - 顯示邏輯 - 參考 [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},會影響到本頁是否要顯示警語 #### 警語相關邏輯 - 目前警語顯示規則如下: - 先透過 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 實際畫面,下圖是結構化後的樣式 - ![](https://i.imgur.com/sC9lfI2.jpg) - ![](https://i.imgur.com/9ffnWfe.jpg) - 圖片結構化後的樣式如下 - ![](https://i.imgur.com/1lM8Xhe.jpg) - 分析取得的警語相關檔案的結構 - 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.modelno | 型錄型號 型錄主要資訊 | lang.madein | 型錄產地 型錄主要資訊 | lang.supplier | 廠商名稱 型錄主要資訊 | lang.ez_moreaboutproduct | More About This Product 型錄主要資訊 | lang.requestforquotation | Request For Quotation 型錄主要資訊 | lang.ez_pleaseselect | Please Select 型錄主要資訊 | lang.ez_available | Available 型錄主要資訊 | lang.contactsupplier | Contact Supplier 型錄主要資訊 | lang.addtofavorites | Add to favorites 型錄主要資訊 | lang.gotofavorites | Go to favorites tab 區塊 | lang.productdetail | Product Detail tab 區塊 | lang.companyprofile | Company Profile 型錄 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.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.recommendation | Recommendation 推薦型錄 | lang.ez_smallrecommendation | Small Order Recommendation