iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0

昨天加入 Google 登入,找店功能仍然開放給未登入的使用者。今天接著把首頁的示意地圖換成 Google Maps。
原本地圖上只有三個固定位置的 Marker(店家標記),會跟著 Filter 顯示或消失。這次改用 Firestore 裡的經緯度放置標記,讓店家卡片能對上地圖中的位置。
資料先沿用原本的示範店,集中處理地圖載入、篩選與選取。

先確認地圖要改哪些地方

動手前,先確認店家能正常載入,登入、登出和搜尋也都能使用,再請 Agent 整理這次的修改計畫:

先閱讀 docs/ 的 CURRENT 規格、README 與 package.json,
並檢查以下檔案;若已移動,找出對應實作:
- resources/js/Pages/Home.tsx
- resources/js/Components/MockMapView.tsx
- CafeCard、FilterChips 與共用版型
- resources/js/types/ 下的店家型別
- app/Services/FirestoreCafeService.php
- 現有 Firebase Authentication 初始化與登入狀態

目前店家由 Laravel 從 Firestore 讀取,經 Inertia props 傳給 React。
請依實際程式確認資料流程,不依賴外部文章。

這次要用 Google Maps 取代示意地圖。
請先提出計畫,不修改程式或雲端設定:
1. 預計替換哪些檔案、沿用哪些 props 與選取狀態。
2. 如何載入 Maps JavaScript API、使用 Advanced Markers,
   以及所需的 API key、Map ID 與環境變數。
3. 如何讓 Marker 使用與列表相同的 filteredCafes,
   只為合法 location 建立標記。
4. 點 Marker 與卡片如何透過 cafe.id 雙向選取。
5. 載入中、失敗、零結果與缺少座標如何呈現。
6. 是否已實作 Bottom Sheet;若沒有,沿用目前卡片列表,
   不在這次順便重構首頁。
7. 手機拖曳、縮放、切換檢視與卸載清理如何驗證。

保留既有 Google 登入、搜尋與 AND 篩選。
不要新增 Places 搜尋、地理編碼、路線或使用者定位。
不要修改 Firestore 文件或補造座標。
列完計畫後先停下,待確認再實作。

啟用 Maps JavaScript API

打開 Google Cloud Console,選擇目前 Firebase 使用的專案。文中的專案識別值以 YOUR_PROJECT_ID 代替。

  1. 確認這個專案已連結有效的帳單帳戶。
  2. 到「API 和服務 → 程式庫」,搜尋 Maps JavaScript API
  3. 開啟 API 頁面,點「啟用」;若已啟用就不用重做。
  4. 到「API 和服務 → 憑證」,建立一把這個網站使用的 API 金鑰。

Maps JavaScript API 需要 API key 與帳單設定,使用量依實際服務計費。這次只顯示地圖和既有座標,不需要為此啟用 Places 或 Routes API。官方設定流程 · 用量與計費

把金鑰限制在自己的網站使用

Google Cloud Console → API 和服務 → 憑證,在 API 金鑰清單點剛建立的金鑰名稱,進入編輯頁:

  1. 在「應用程式限制」選 Websites/網站
  2. 加入允許使用這把金鑰的網站網址,也就是 HTTP referrer。
  3. 在「API 限制」選 限制金鑰,勾選 Maps JavaScript API
  4. 確認網址與 API 都選好後,按「儲存」。

例如使用 http://localhost:8000 測試,就加入:

http://localhost:8000/*

網址要與瀏覽器實際開啟的主機名稱、連接埠一致。這裡是 Maps 金鑰的網站限制,和昨天 Firebase Authentication 的授權網域分開設定。
這把金鑰會隨地圖請求送到瀏覽器。環境變數方便管理設定,使用範圍則由網站與 API 限制來控制。API key 設定說明

📸 圖片 1-1|Maps JavaScript API 已啟用
https://ithelp.ithome.com.tw/upload/images/20260920/20121296cbQBK69GUC.png

📸 圖片 1-2|Maps 金鑰的限制設定
https://ithelp.ithome.com.tw/upload/images/20260920/20121296z7EleyTor8.png

準備 Map ID 與前端設定

接著準備 Advanced Markers 所需的 Map ID。到 Google Maps Platform 的「地圖管理/Map Management」,建立 JavaScript 類型的地圖並取得 Map ID。本機初步測試也可使用官方的 DEMO_MAP_ID,正式使用前再換成自己的設定。Advanced Markers 說明
Map ID 是地圖設定的識別值,與 Project ID 不同。依 Agent 計畫中列出的變數名稱,把 Maps API key 與 Map ID 填進專案的 .env。例如:

VITE_GOOGLE_MAPS_API_KEY=YOUR_MAPS_API_KEY
VITE_GOOGLE_MAPS_MAP_ID=YOUR_MAP_ID

實際值只填在本機,文章保留上面的佔位值。變數名稱要和程式一致,Firebase Authentication 的原有設定則繼續保留。
下一步實作完成後,重新啟動 Vite,讓前端讀到新設定;若使用建置檔,就重新執行 build。服務帳戶私鑰仍留在後端。

用現有座標畫 Marker

原本五間示範店中,四間有座標,一間的 location 是 null。若資料沒有另外修改,清除篩選後應該看到五張卡片、四個 Marker。
缺少座標的店保留在列表,顯示位置未知。篩選時,地圖使用和列表相同的結果,再從中挑出有合法座標的店畫上標記。
計畫與設定確認後,再交給 Agent 實作:

請在現有 Laravel+Inertia+React 專案,
以 Google Maps 取代示意地圖,保留現有店家資料與 Google 登入。

先閱讀 docs/ 的 CURRENT 規格、README、package.json,
檢查 Home.tsx、MockMapView、CafeCard、FilterChips、
共用版型、店家型別與 FirestoreCafeService。
若路徑不同,找出實際對應檔案。

店家資料仍由 Firestore → Laravel → Inertia props → React。
使用 Maps JavaScript API 與 Advanced Markers,
確認 API key、Map ID 及對應環境變數已設定。
缺少設定時回報,不猜測金鑰,不自行建立雲端資源。

地圖沿用 filteredCafes,不另寫一套搜尋或篩選。
只為 location 中合法的數值 lat/lng 建立 Marker,
檢查經緯度範圍,不接受數字字串,不補造座標。
缺少座標的店保留卡片,標示位置未知,不建立 Marker。
保留示範資料標示;真實地圖底圖不代表示範店家已查核。

以 cafe.id 共用選取狀態:
- 點 Marker,對應卡片選取並能看見。
- 點有座標的卡片,對應 Marker 高亮並移到可見範圍。
- 點無座標卡片,不移動地圖到假位置。
- 已選店家被篩除時清除選取;取消條件不自動恢復選取。
- 被篩除的 Marker 移除,包含其事件與舊選取狀態。

處理地圖載入中與失敗,失敗時仍可使用店家列表。
零結果時清除 Marker 並顯示既有空結果提示。
避免重複載入 SDK、重複 Marker 與事件監聽;
切換檢視或卸載時清理資源。
不要在每次 render 時重設地圖中心,打斷使用者拖曳。
保留 Google 地圖標誌與必要標示。

未登入仍能使用地圖、搜尋和 Filter。
沿用現有版型;若尚無 Bottom Sheet,本次不要額外實作。
不串接 Places、Routes、地理編碼或使用者定位。
不改 Firestore 文件、登入流程或既有篩選條件。

執行 typecheck、build 與必要測試,
回報修改檔案、實際驗證結果與未驗證事項。
不要輸出金鑰、私鑰或 token,不自動 commit 或 push。

📸 圖片 2|現有店家出現在 Google Maps 上
https://ithelp.ithome.com.tw/upload/images/20260920/201212960kKGqH5fjo.png

點地圖或卡片,都要選到同一間店

地圖載入後,先挑一間有座標的店測試。點 Marker,確認對應卡片被選取;再點另一張卡片,確認地圖改為高亮那間店,前一個標記也取消選取。
接著把已選取的店篩掉,再取消條件。Marker 應該重新出現,但不會自動恢復原本的選取。這段要沿用之前的行為。

📸 圖片 3-1|點 Marker,選到對應卡片
https://ithelp.ithome.com.tw/upload/images/20260920/201212960kKGqH5fjo.png

📸 圖片 3-2|點另一張卡片,地圖同步選取
https://ithelp.ithome.com.tw/upload/images/20260920/20121296s2V045mHBI.png

手機尺寸再跑一次

最後在瀏覽器開發者工具切到裝置模式,把寬度設為 390px,檢查地圖、卡片和控制按鈕是否擠在一起,再測拖曳、縮放與選取。有手機的話,也補一次觸控操作。
下面是要逐項核對的預期結果,尚未執行的項目先記為未驗證:

操作 預期結果
未登入開啟首頁 地圖與店家列表可使用
搜尋、單一 Filter、多條件 AND Marker 對應篩選結果中有座標的店
篩選到零結果 Marker 全部移除,出現空結果提示
選店 → 篩除 → 取消條件 Marker 恢復,但不自動恢復原選取
點選無座標店家 卡片可查看,地圖不跳到虛構位置
切換列表與地圖 不出現重複 Marker,選取狀態與目前卡片一致
拖曳、縮放、操作卡片 地圖和頁面捲動可正常使用,控制按鈕不被遮住
地圖載入失敗 顯示錯誤提示,仍能查看店家列表
登入、登出後繼續找店 搜尋、篩選與地圖維持正常

地圖接好後,還要看操作是否順手

這次的範圍是把既有店家放上 Google Maps,讓篩選結果、Marker 和卡片選取保持一致。驗證時除了看標記有沒有出現,也要確認拖動地圖、切換檢視或取消篩選後,畫面仍照預期運作。
目前仍使用示範資料,也還沒有加入使用者定位與路線計算。先把這段操作做好,再接著處理真實店家與附近搜尋。


上一篇
Day 18|加入 Google 登入,找咖啡廳還是不用先登入
下一篇
Day 20|從一間店開始,試試 Google Places 能提供哪些資料
系列文
咖啡、Wi-Fi 與 AI:30 天打造數位遊牧工作地圖20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言