Appearance
全局搜索(開發文檔)
最後更新:2026-04-10
📖 功能說明請參考 全局搜索
架構
相關檔案
ViewController / ViewModel(3 個)
| 檔案 | 說明 |
|---|---|
.../GlobalSearch/GlobalSearchViewController.swift | 搜索主頁 |
.../GlobalSearch/GlobalSearchViewModel.swift | 搜索 ViewModel |
.../GlobalSearch/Shared/SearchHeader/GlobalSearchHeaderViewModel.swift | Header ViewModel |
基礎路徑:
BBSport/Tab/
Service(3 個)
| 檔案 | 說明 |
|---|---|
.../Section/SearchHistory/GlobalSearchHistoryService.swift | 搜索歷史 |
.../Section/HotSearch/GlobalSearchHotSearchService.swift | 熱門搜索 |
.../Section/Search/GlobalSearchService.swift | 搜索請求 |
基礎路徑:
BBSport/Tab/GlobalSearch/
View(31 個)
| 檔案 | 說明 |
|---|---|
.../Shared/SearchField/GlobalSearchField.swift | 搜索輸入框 |
.../Shared/SearchField/GlobalSearchFieldInputUseCase.swift | 輸入框 UseCase |
.../Shared/SearchHeader/GlobalSearchHeaderView.swift | 搜索 Header |
.../Shared/SelfSizingCollectionView.swift | 自適應 CollectionView |
.../Shared/LeftAlignedCollectionViewFlowLayout.swift | 左對齊 Layout |
.../Section/HotSearch/View/GlobalSearchHotSearchView.swift | 熱門搜索視圖 |
.../Section/Search/View/Match/GlobalSearchMatchResultView.swift | 賽事結果 |
.../Section/Search/View/Activity/GlobalSearchActivityResultView.swift | 活動結果 |
.../Section/Search/View/Disclosure/GlobalSearchDisclosureResultView.swift | 爆料結果 |
.../Section/Search/View/Game/GlobalSearchGameResultView.swift | 遊戲結果 |
.../Section/Search/View/Host/GlobalSearchHostResultView.swift | 主播結果 |
.../Section/Search/View/Empty/GlobalSearchEmptyResultCell.swift | 空結果 |
.../Section/SearchHistory/View/GlobalSearchHistoryView.swift | 搜索歷史 |
| 以及各 +ItemCell / +StateItem 擴展檔... |
基礎路徑:
BBSport/Tab/GlobalSearch/
Protocol / Model(5 個)
| 檔案 | 說明 |
|---|---|
.../Protocol/GlobalSearchSectionService.swift | Section Service 協議 |
.../Protocol/GlobalSearchCellStateItem.swift | Cell 狀態協議 |
.../Protocol/GlobalSearchSectionFooterStateItem.swift | Footer 狀態協議 |
.../Protocol/GlobalSearchSectionHeaderStateItem.swift | Header 狀態協議 |
.../Section/Search/Model/GlobalSearchTab.swift | Tab 分類 Model |
基礎路徑:
BBSport/Tab/GlobalSearch/
API
搜索流程
熱門搜索聯賽
POST api/forehead/data/user/global/search/hotLeague — urlForm — STAPI.GlobalSearchHotSearchRequest
無參數,靠 Header token/uid 認證。
Response: [League]
| 欄位 | 型別 | 說明 |
|---|---|---|
| leagueId | String | 聯賽 ID |
| leagueName | String | 聯賽名稱 |
| leagueLogo | String | 聯賽 Logo URL |
| matches | [Match] | 旗下賽事列表 |
Match 欄位:home(String), away(String), homeScore(Int?), awayScore(Int?), eventId(String), startEventDate(Double, 毫秒)
搜索結果
POST api/forehead/data/user/global/search — urlForm — STAPI.GlobalSearchRequest
| 參數 | 型別 | 必填 | 說明 |
|---|---|---|---|
| searchKey | String | ✅ | 搜索關鍵字 |
| showClient | String | ✅ | 固定 "2"(App 端) |
| tabIndex | GlobalSearchTab | ✅ | Tab 篩選(-1=全部) |
| size | String | ✅ | 固定 "10" |
Response:
| 欄位 | 型別 | 說明 |
|---|---|---|
| leagues | [League] | 聯賽與賽事 |
| liveHosts | [LiveHost] | 主播列表(含 hasLive) |
| gameVenues | [Game] | 遊戲場館 |
| activities | [Activity] | 優惠活動 |
| activityDisclosures | [Disclosure] | 活動爆料 |
| sportsMessages | [SportsMessage] | 運動爆料/專家推薦 |
| hasDataIndex | String | 有資料的 Tab 索引(逗號分隔) |
搜索子 Model 欄位
LiveHost(主播)
| 欄位 | 型別 | 說明 |
|---|---|---|
| hasLive | Int | 是否開播(1=是、0=否) |
| id | Int | 主播 ID |
| name | String | 主播名稱 |
| photoId | String | 主播頭像圖片 URL |
Activity(優惠活動)
| 欄位 | 型別 | 說明 |
|---|---|---|
| startTime | Double | 活動開始時間(毫秒) |
| endTime | Double | 活動結束時間(毫秒) |
| id | Int | 活動 ID |
| name | String | 活動名稱 |
| pcImage | String | Banner 圖片 URL |
| pcUrl | String | 跳轉路徑 |
Disclosure(活動爆料)
| 欄位 | 型別 | 說明 |
|---|---|---|
| h5Url | String | H5 跳轉連結 |
| createTime | Double | 創建時間(毫秒) |
| actId | Int | 跳轉連結 ID |
| id | Int | 爆料 ID |
| isHot | Bool | 是否熱門 |
| recommended | Bool | 是否推薦 |
| replyCount | Int | 回覆數 |
| thumb | Int | 按讚數 |
| title | String | 爆料標題 |
| username | String | 用戶名稱 |
| titlePic | String | 用戶稱號圖片 |
| vipLevel | Int | 用戶 VIP 等級 |
SportsMessage(運動爆料/專家推薦)
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | Int | 爆料 ID |
| professorName | String | 專家名稱 |
| sportsTitle | String | 爆料標題 |
| goldPrice | Int | 金幣價格 |
| sportsPlay | String | 比賽玩法 |
| openBackGold | Int | 黑單返金開關(1=開) |
| profile | String | 頭像 URL |
| introduction | String | 專家介紹 |
| tag | String | 編輯標籤 |
| redContinuous | Int | 連紅數 |
| sportsId | Int | 賽事 ID |
| isBuy | Int | 是否已購買(1=是) |
| result | Int | 爆料結果(0=未結束、1=紅、2=黑、3=和...) |
| isAttention | Int | 是否關注(1=是) |
| yieldRate | String | 收益率(含%) |
| playDetails | [PlayDetail] | 玩法細節(content、odds、selected) |
| home / guest | String | 主隊/客隊名稱 |
| sportsName | String | 聯賽名稱 |
| sportsTime | Double | 開賽時間(毫秒) |
搜索歷史
GlobalSearchHistoryService 使用 STPerUserStorage<[String]>(UserDefaults 封裝,每用戶獨立 key)儲存搜索歷史。
| 規則 | 數值 |
|---|---|
| 最大儲存筆數 | 20 筆 |
| 預設展示行數 | 2 行,超過折疊,點擊「展開」查看全部 |
| 儲存方式 | STPerUserStorage(UserDefaults,key: GlobalSearchHistory.Key) |
| 重複處理 | 重複關鍵字移到第一位,達上限時移除最舊一筆 |
| 清除 | 彈出確認 Alert,確認後清除全部 |
實作重點
- 三段 Service 架構:History(本地)、HotSearch(API 熱門)、Search(搜索)各自獨立建構 Section
- Combine 驅動:
@Published loadState驅動 UI;DispatchGroup並發請求後統一更新 - 防抖機制:
GlobalSearchFieldInputUseCase控制輸入限制:最少 2 個字元 才觸發搜索,每次搜索間隔至少 2 秒(requestTimeInterval = 2),超過 50 字元 無法輸入。非 debounce/throttle 實作,而是在shouldRequestSearch()中以lastRequestTime差值判斷 - Tab 篩選:
GlobalSearchTab支援全部/足球/籃球/網球等,filter(by:)按球類過濾 - CollectionView 自適應:
LeftAlignedCollectionViewFlowLayout左對齊 +SelfSizingCollectionView自動高度 - 外部帶入搜索:
init(searchText:)支援從其他頁面跳轉帶入初始關鍵字 - 搜索失敗 UI:API 請求失敗時(
onErrorcallback),loadState不更新為.end,UI 保持.searching狀態。搜索有結果但特定分類無資料時顯示GlobalSearchEmptyResultCell空狀態。若搜索結果完全為空(isEmpty=true),則同時顯示空結果提示和歷史/熱門搜索區塊