接 Spring Boot 前已將前端整包收進 frontend/,根目錄邊界如下:
| 路徑 | 放什麼 | 你要改…時來這裡 |
|---|---|---|
frontend/ |
主站、booking、admin、mock data/、Vite/npm |
網頁、樣式、假資料、前端腳本 |
backend/ |
Spring Boot | Java API(線 A 骨架:Firebase ID Token;見 backend/README.md) |
docs/ |
Schema、前端規格、資料表說明 | DB/規格文件 |
plans/ |
規劃與遷移規格 | 例如 plans/frontend-folder-migration-spec.md |
docker-compose.yml + .env.example |
本機 PostgreSQL | 資料庫基礎設施 |
後端程式使用簡短中文註解;流程文件集中在 docs/backend-specs/,只保留用途、主要流程與驗證結果。
GET /api/products 與 GET /api/products/{id} 已完成;B-3 PostgreSQL 分頁、id/name 排序、參數錯誤 Envelope 與實際 Controller 驗收已通過。docs/backend-specs/catalog/b3-product-pagination-validation.md。variants[] 只回 active variant;規格層級可售庫存尚未實作,需先升版契約並建立 variant 庫存讀模型。docs/backend-specs/catalog/b5-product-variants-stock-status.md。orders、order_items、product_stock_reservations Entity 已通過 Docker PostgreSQL 與 Hibernate ddl-auto=validate。docs/backend-specs/order/c1-entity-schema-validation.md。docs/backend-specs/checkout/README.md。expired 並釋放庫存;PostgreSQL 逾時與冪等驗收已通過。bookings 加入 Checkout 冪等 key、request hash 與會員範圍唯一約束。POST /api/booking/check-availability 依 Asia/Taipei 政策驗證日期,並計算公休、停售及既有預約占用後的跨晚最低剩餘量。POST /api/booking/checkout/sessions 會以固定順序鎖位、重查可用量、後端計價,並建立 15 分鐘的 pending/unpaid 預約;冪等與並發防超賣已通過 PostgreSQL 測試。BookingAPI 在 Backend 模式統一呼叫 /api/booking/**,可用性、價格、Booking ID、本人列表/詳情/取消與 15 分鐘倒數都使用後端結果;不再寫入 mockBookings 或自行標記 paid。線 E 定義為 Booking Prepare/Reservation 完成,ECPay 與付款確認延後線 D。AppAuth.getIdToken() 統一取得 Firebase/開發 Token,ApiClient._restRequest() 統一處理 Bearer、Envelope、meta 與後端錯誤。window.API.checkout 已提供建立、讀取、更新、取消、COD 與 ECPay 六個契約方法;adapter 路徑不重複加入 /api。CheckoutSession:Mock 由商品契約重算價格、支援冪等並寫入獨立 mockCheckoutSessions;Backend 模式禁止 Legacy orders.create()。API.checkout.createSession();Request 不再傳會員 ID、商品快照、前端價格、總額、狀態或點數,會員由 Firebase principal 決定,金額由 Spring Boot 從 PostgreSQL 重算。crypto.randomUUID() 產生並暫存在 sessionStorage;網路重試與連點沿用同一 key,成功保存後端 orderId,購物車變更、取消或逾時才清除。CheckoutSession.pricing;Backend 模式不建立 Legacy Order、不消耗前端優惠券,ECPay 也不在本站收集卡號、到期日或 CVV。DEV-STORE-MAIN 與 V001 的 10 件庫存,可直接從 Swagger 驗證 Checkout。前端的 npm/Vite 根目錄是 frontend/,必須先進入該資料夾再啟動。
頁面使用網站根絕對路徑(/storefront/...、/data/...、/assets/...),因此伺服器根必須是 frontend/,用 Vite 最穩。
# 在 repo 根 Yuruicamp/ 執行:
cd frontend
npm install # 第一次或依賴有變時
npm run dev # 啟動 Vite 開發伺服器
終端機出現類似 http://127.0.0.1:5173 後,用瀏覽器開啟:
| 要看什麼 | 網址 |
|---|---|
| 品牌入口(會導向首頁) | http://127.0.0.1:5173/ |
| 主站首頁 | http://127.0.0.1:5173/storefront/pages/home.html |
| 商品列表 | http://127.0.0.1:5173/storefront/pages/products.html |
| 營地預約 | http://127.0.0.1:5173/booking/pages/camp-search.html |
| 賣家後台 | http://127.0.0.1:5173/admin/login.html |
停止伺服器:在該終端機按 Ctrl + C。
常見錯誤(會導致沒有 CSS/JS、後台抓不到假資料):
Yuruicamp/ 直接 npm run dev(這裡沒有前端的 package.json 工作根)frontend/storefront/pages/...(此時 /storefront、/data 會 404)file:// 直接雙擊開 HTML(絕對路徑無法正確指向資源)假資料: 檔案在 frontend/data/;瀏覽器執行期路徑是 /data/**(由 Vite 以 frontend/ 為 root 提供)。
開發者改檔對照請看 userguide.md。更完整的啟動說明見下方「啟動方式」。
後端開發使用 PostgreSQL 16。為了讓大家環境一致,資料庫用 Docker 啟動;
Spring Boot 仍建議在本機 IDE 執行(除錯比較方便)。
相關檔案:
| 檔案 | 說明 |
|---|---|
docker-compose.yml |
只啟動 Postgres(不包前端/後端) |
.env.example |
環境變數範本(可進 Git) |
.env |
每人本機密碼(不要 commit;已在 .gitignore) |
在專案根目錄複製環境變數檔:
Copy-Item .env.example .env
用編輯器打開 .env,把 POSTGRES_PASSWORD 改成你自己的本機密碼。
不要把 .env commit / push 到 GitHub。
啟動資料庫:
docker compose up -d
確認容器有在跑:
docker ps
應看得到 yuruicamp-db,且 PORTS 類似 0.0.0.0:5433->5432/tcp。
| 項目 | 值 |
|---|---|
| Host | localhost |
| Port | 5433(不是 5432) |
| Database | yuruicamp |
| Username | .env 裡的 POSTGRES_USER(預設 postgres) |
| Password | .env 裡的 POSTGRES_PASSWORD |
Spring Boot 範例(之後放在本機設定,勿把真密碼推進 Git):
spring.datasource.url=jdbc:postgresql://localhost:5433/yuruicamp
spring.datasource.username=postgres
spring.datasource.password=你的密碼
# 啟動
docker compose up -d
# 看 log(排查連線問題很有用)
docker compose logs -f yuruicamp-db
# 停止(資料還在)
docker compose down
# 停止並清空資料卷(會刪光 DB 資料,慎用)
docker compose down -v
compose 在資料卷第一次建立時,會自動執行:
docs/latest_schema.sql(現行唯一 DDL;破壞性整檔重建)docs/seed/002-dev-seed.sql(開發資料唯一入口;依序載入 docs/seed/dev/)說明文件:
docs/database-schema-guide.md(ER/資料字典導覽)docs/schema-enums.md(ENUM 允許值)docs/database-documents/(各領域業務說明)若 volume 已存在、只改了 SQL,需重建(會清資料):docker compose down -v 後再 up -d。
也可對空庫手動用 DBeaver / pgAdmin / psql 執行 docs/latest_schema.sql。
Q: port is already allocated / 5433 被占用?
A: 改 .env 的 POSTGRES_PORT(例如 5434),並同步改 Spring Boot 的連線 port。
Q: 我改了 .env 密碼或需要重建最新資料庫結構,連線還是舊資料?
A: Postgres 的帳密與 docs/latest_schema.sql 都只在「資料卷第一次建立」時套用。若要重建(會清資料):
docker compose down -v
docker compose up -d
Q: 為什麼不用本機 5432?
A: 很多人電腦已裝過 PostgreSQL,5432 容易衝突,所以預設用 5433。
Q: 可以把密碼寫在 docker-compose.yml 再推 GitHub 嗎?
A: 不行。請用 .env(已在 .gitignore),範本用 .env.example。
| 文件 | 說明 |
|---|---|
docs/latest_schema.sql |
PostgreSQL 現行 DDL(建庫真相來源) |
docs/database-schema-guide.md |
ER 圖、函式/Trigger、資料字典 |
docs/schema-enums.md |
status / category 等 ENUM 允許值 |
docs/database-documents/ |
各業務表領域說明 |
docs/seed/README.md |
PostgreSQL 開發 Seed:SQL 結構、載入順序、執行方式與維護規則 |
plans/data-integration-spec.md |
前端 Mock JSON:資料語意、關聯、衍生資料與維護規則 |
plans/schema-migration-checklist.md |
Schema 整合任務清單(歷史勾選;DDL 以 latest_schema 為準) |
cd frontend
npm run validate:data
npm run sync:listings
npm run normalize:data
#personalizationModal、#surveyCloseConfirmModal 樣式來源,booking components 入口改載主站 modal 與 auth-modal 基底。booking/js/layout.js 不再替偏好問卷與關閉確認視窗加入 bookingAuth* 視覺 class,只保留 #loginModal 的 booking 登入差異 class。booking/css/components/_booking-auth-modal.scss,移除 booking 問卷、stepper、tag 與確認視窗覆寫,讓偏好問卷與確認視窗回到主站共用樣式。floatingLineBtn hover 動畫:提示膠囊改從 icon 原位向左展開,符合 LINE 聯絡膠囊樣式。clip-path、opacity、transform 顯示,避免改變 layout 寬度。js/main.js 改輸出 floatingActions、floatingTopBtn、floatingLineLabel、floatingLineIcon 等 camelCase selector。css/components/widgets/_floating-actions.scss 成為主站與 booking 共用的唯一 floatingActions 樣式來源,booking components 入口改為引用此 widgets partial。booking/css/components/_floating-actions.scss,並重新編譯主站與 booking CSS 輸出,讓兩邊顯示同一套回頂部與 LINE 聯絡操作。button.modalClose.sharedAuthClose 關閉當下會重新記錄 scroll 位置,再關閉 Modal 並還原位置與觸發按鈕焦點。button.siteCartButton 開啟購物車 Drawer 時會記錄目前頁面位置,Drawer 聚焦關閉按鈕後會還原原本 scroll。button.siteCartDrawerClose 關閉購物車 Drawer 時會記錄關閉當下位置,關閉後以 preventScroll 回到原觸發按鈕,避免畫面跳至頂部。siteMenuButton 開啟 / 關閉 offcanvas 時可能跳回頁首的問題,開啟前會記錄 scroll 位置,關閉後還原位置並以 preventScroll 回焦點。siteLoginButton 開啟 / 關閉登入 Modal 時可能改變瀏覽位置的問題,登入 Modal 的關閉按鈕、背景遮罩與 Esc 關閉都會保留原 scroll。js/components/header.js 新增 Header 互動層位置還原工具與中文區塊註解,避免共用 modal 關閉事件造成頁面位移。partnerModalClose、背景遮罩與 Esc 關閉都會保留開啟前的 scroll 位置。js/pages/branches.js 新增 partner modal 專用關閉流程,先攔截共用 modal 關閉事件,再呼叫 closeModal() 並於下一幀還原 scroll 與觸發按鈕 focus。--shipping-progress-value 為可動畫 percentage,讓深色填滿區在購物車增加、減少或清空時平滑推進 / 回退。shippingProgressValue0~100 class 與 --yc-* token,不新增 inline style 或新色碼。prefers-reduced-motion 會套用既有 reduced motion 規則,降低進度變化動畫時間。<progress> 的瀏覽器 pseudo element 來顯示填滿色,改由 .shippingProgressBar 本體背景搭配 shippingProgressValue0~100 class 繪製進度比例。<progress> 仍保留 value 與 aria-valuenow 作為語意與無障礙狀態,視覺則由 SCSS 使用既有 --yc-* token 控制。docs/ai-style-sheet.md 使用既有 --yc-* token,為商品詳情頁免運進度條新增 isEmpty、isInProgress、isComplete 三種狀態。_renderShippingProgress() 統一切換,並補齊 prefers-reduced-motion 對進度條本體 transition 的處理。#shippingProgressBar 不再於購物車為空時使用假進度值,改與實際購物車小計計算出的百分比同步。value 與 aria-valuenow,讓清空購物車、減少數量與移除商品後,視覺填滿比例、文字百分比與輔助資訊保持一致。yurui:cart-changed,在加入、移除、數量異動與清空購物車後統一廣播最新購物車狀態。pages/product-detail.html 的免運進度條現在會監聽購物車變更事件,清空購物車、減少商品數量或移除商品時會即時更新 #shippingProgressBar、百分比文字與免運提示。transitionend 同一個渲染週期內關閉 transition、重設 transform、強制 reflow,下一幀才恢復 transition,讓跳回真實 slide 的動作不被畫出。_cleanupAdCarousel(),偏好更新後若沒有推薦商品會清除舊 timer、transition fallback 與事件監聽。adCarouselContainer 動畫啟動改用 double requestAnimationFrame,並補上 transition fallback timer,避免瀏覽器合併 layout 或 transitionend 遺失時讓輪播卡在 isAnimating。adCarouselContainer 改為三張視窗式循環渲染:每次只渲染上一張、目前張、下一張,動畫結束後重建視窗並維持在中間位置。adCarouselContainer 廣告輪播循環:改為首尾雙 clone,最後一張往第一張與第一張往最後一張都透過無動畫重定位完成,避免出現倒回式瞬間切換。js/pages/product-list.js 的輪播控制新增有界 index 與 AbortController,重新初始化偏好推薦輪播時會中止舊事件監聽,避免多次綁定後 index 持續位移到不存在的 slide。.agents/agents.md 調整首頁 CTA token:heroPrimaryLink 改用 --yc-cta / --yc-on-cta,hover 改用 --yc-cta-hover / --yc-on-cta-hover。heroSecondaryLink 的 border 與文字同步改用 CTA token,hover 狀態改用 CTA hover token,維持透明背景不改動版面。homeProductAddButton 改用 CTA token 作為背景、邊線與文字色,並補上 --yc-on-cta-hover 至主站、booking 與 AI token 文件。booking/booking-style-tokens.md 的 booking token、互動規則、z-index、元件規則與 AI 檢查清單整併進 docs/ai-style-sheet.md,讓 AI 樣式規範成為單一 source of truth。booking/booking-style-tokens.md,並更新 docs/ai-style-tokens.css 與 plans/booking-itcss-scss-plan.md 的現行參考路徑。docs/ai-style-sheet.md 的實作 prompt 改為要求閱讀 docs/ai-style-sheet.md 與 docs/ai-style-tokens.css,並明確禁止新增 --bk-* / --yui-* alias。:first-child / :last-child 結構 selector 改為相鄰兄弟 selector 或明確語意 class,降低清單尾端與 DOM 順序相依。transition: all 改為明確 transition property,避免尺寸與 spacing 被動畫化。--yc-* 為唯一 runtime token source。css/settings/_tokens.scss 與 booking/css/settings/_tokens.scss 中的 --yui-* / --bk-* 相容 alias,runtime token source 統一只保留 --yc-*。booking/booking-style-tokens.md、docs/ai-style-tokens.css、docs/ai-style-sheet.md、docs/itcss-architecture.md 與 booking/css/semantic-selector-map.md,不再把舊 alias 當成可用規格。bookingToast 動畫 keyframes 與 booking cart spec 的 bk* 文件殘留,讓新規範掃描聚焦在正式命名。docs/frontend-specs 與結帳成功頁內嵌腳本改用 --yc-* token,來源檔不再直接引用舊 --yui-*。active 收斂為 isSelected / isOpen,並調整輪播指示器為固定尺寸,避免狀態切換造成版面位移。--yc-*:css/settings/_tokens.scss 與 booking/css/settings/_tokens.scss 皆定義同一套 base、status、layout、typography、spacing、radius、shadow、motion 與 z-index token。--yui-* 與 --bk-* 相容 alias;主站 SCSS 使用點已從 --yui-* 逐步改為 --yc-*,舊 selector 仍可透過 alias 過渡。booking/booking-style-tokens.md 補齊 alias mapping,docs/ai-style-tokens.css 改為 alias 文件,docs/ai-style-sheet.md 與 docs/itcss-architecture.md 同步標明 --yc-* 為新的樣式規格來源。npm.cmd run stylelint、npx.cmd sass css/main.scss:css/main.css booking/css/booking-main.scss:booking/css/booking-main.css --style=expanded --source-map、npm.cmd run smoke、npm.cmd run build 通過;dev server 在本環境可短暫回應 /,但後續頁面 HTTP 批次讀取時 port 未保持 listening,完整瀏覽器頁面 QA 未完成。summaryBar summaryBarBooking、recommendationBanner recommendationBannerBooking、rentalLayout rentalLayoutBooking、rentalCartSidebar rentalCartSidebarBooking 等。camp-rental.js 產生的摘要分隔符與推薦橫幅內容同步改為 summarySeparator*、recommendationBannerContent*、recommendationTag* 語意命名,並保留既有 ID 作為資料與事件掛點。booking/css/pages/_camp-rental.scss 改由 *Booking selector 管理租借頁外殼、推薦橫幅、列表 grid、右側背包與略過連結,避免 page style 綁在可共用 base class 上。zoneCard zoneCardBooking、zoneCardInfo zoneCardInfoBooking 等 base + Booking variant 雙 class,事件代理同步改抓 Booking 變體。rentalItemCard* 共通語意搭配 rentalItemCard*Booking 變體,並將 rentalItemCardImg / rentalItemCardDesc 收斂為較完整的 Image / Description 命名。rentalCartEmpty*、rentalCartItemName*、rentalCartItemPrice*,SCSS 不再依賴 span:first-child / span:nth-child(2) 結構 selector。camp-detail.js 的 zoneSelectBtn 改為 zoneSelectButton zoneSelectButtonBooking,camp-rental.js 的 rentalAddBtn / rentalRemoveBtn 改為 rentalAddButton rentalAddButtonBooking / rentalRemoveButton rentalRemoveButtonBooking。booking/css/pages/_camp-detail.scss 與 _camp-rental.scss 同步改用新 Button selector,並補上中文註解說明互動 hook 與操作責任。camp-search.js 動態營區卡片補齊 campCard* 共通語意與 campCard*Booking 變體 class,_camp-search.scss 改由 Booking 變體 selector 管理搜尋結果卡片視覺。bk* 收斂為 booking* 語意命名:bookingMenuButton、bookingOffcanvasPanel、bookingOffcanvasBackdrop、bookingCartButton 與 bookingPanelBackdrop。components/header.partial 同步更新 aria-controls 與對應 ID,booking/js/booking-header.js 改用新 ID 查找側邊選單、預約背包面板與 backdrop。booking-header.js 在側邊選單與預約背包面板開關區塊補上中文註解,說明互動掛點與 header layer 狀態責任。booking-utils.js 不再產生 bkToast* / bkToast--type class,統一輸出 bookingToast、bookingToastInfo、bookingToastText、bookingToastClose 等語意化 class。booking/css/components/_booking-toast.scss 移除 #bkToastContainer、.bkToast*、.bkToastAction* 相容 selector,只保留 bookingToast* 正式命名與中文區塊註解。bookingToastConfirm / bookingToastActions / bookingToastAction* 樣式作為共用操作型通知能力,但不再提供舊 bkToast* alias。#bk* ID 掛點改為 #bookingCart* 語意命名,例如 bookingCartContent、bookingCartStayBody、bookingCartCostRows 與 bookingCartCheckoutButton。booking-cart.js 同步改用新 ID selector,保留既有 localStorage、數量調整、刪除裝備與費用重算流程不變。booking/css/pages/_booking-cart.scss 移除 #bkRentalCard + div 結構相依 selector,新增 cartClearAction cartClearActionBooking,讓清除背包操作列有明確語意與中文註解。bkCheckout*、bkPanel*、bkSummaryCard、bkLoginNotice* 改為 checkout* / summaryCard* / loginNotice* 共通語意加 *Booking 變體 class。booking-checkout.html 的頁面根 class 從 bookingCartPage 分離為 bookingCheckoutPage,避免結帳頁繼續借用背包頁語意;既有表單與流程 ID 保留作為 JS 掛點。booking-checkout.js 的手風琴事件代理改用 checkoutPanelHeaderBooking、checkoutPanelBooking 與 checkoutPanelBodyBooking,並在 SCSS 補上中文區塊註解說明 layout、panel、summary、login notice 與送出區責任。cart* / quantity* 共通語意加上 *Booking 變體 class,例如 cartItem cartItemBooking、quantityButton quantityButtonBooking。#bk* ID 作為既有 JS 掛點,避免本輪命名收斂牽動 localStorage 與頁面流程;樣式與事件代理已不再依賴舊 .bkCart* / .bkQty* / .bkRentalRemove class。booking/css/pages/_booking-cart.scss 補上中文區塊註解,說明 cart layout、項目列、數量調整器、摘要欄與空狀態的責任邊界,供後續 booking checkout / rental 頁面套用同一命名規則。rental-guide、booking-faq、租借、購物車與結帳頁面改用既有 --yc-* / --bk-* token,移除可替代的 var(..., #fallback) 與一般白色硬碼。color-mix() token 表達,移除未使用的 .bkToastInfo / .bkToastWarning / .bkToastError / .bkToastSuccess 舊狀態 selector。booking/css/objects/_layout.scss、_booking-layout.scss 與 utilities/_utilities.scss,objects 層保留為低語意 layout object 的入口說明。booking/css/components/_floating-actions.scss,搜尋頁 hero 樣式移入 booking/css/pages/_camp-search.scss,避免 component/page 樣式留在 objects 層。booking-utils.js 改採 bookingToast* 新命名,同時保留舊 bkToast* / bkToast--type 相容 class,修正原本 JS type class 與 CSS 狀態 selector 不一致的問題。searchPage、detailPage、rentalPage、bookingCartPage 與對應 layout grid 從 objects 層移回 pages 層,objects barrel 只載入低語意版面結構。booking/css/components/_modal.scss 改載主站 modal 共用骨架,booking-auth-modal 保留 booking 登入側滑、OAuth 與偏好問卷差異,並移除未被 runtime 使用的 legacy OAuth / personalization selector。booking/css/settings/_tokens.scss,並把唯一 .srOnly helper 收斂至 generic reset,booking 入口不再額外載入 utilities 層。.agents/agents.md 收斂主站與 booking 的 header/footer 樣式來源:booking header、footer、drawer 改為載入主站共用 widgets / drawer / offcanvas / cart-drawer 骨架,內容仍由 data-layout-part 各自分流。booking/css/settings/_tokens.scss 新增 --yui-* 相容 alias,對應既有 --yc-* token,讓共用樣式在 booking runtime 可直接使用而不新增色碼、字體或間距系統。.bk* / .bkFooter* 相容樣式,保留 #bkHamburger、#bkCartBtn 等 booking JS 既有功能 hook。.agents/agents.md 完成 booking ITCSS 歸層審查:將搜尋、詳情、結帳支援、裝備租借等單頁 selector 從 booking/css/components/ 移入 booking/css/pages/,並新增 booking/css/pages/_camp-rental.scss。booking/css/base.css 與 booking/css/booking.css;公開頁維持載入 Sass 編譯輸出 booking/css/booking-main.css。--yc-* 仍為 booking token source of truth、--bk-* 只作相容 alias,並同步更新 docs/itcss-architecture.md、plans/booking-itcss-scss-plan.md 與 README 的 booking CSS 架構描述。components/member-center.partial、js/components/member-center.js 與主站會員中心樣式。returnTo 參數,會員中心「返回首頁」會回到各自來源頁;若來源不在允許範圍內則回主站 home.html 或 booking camp-search.html。.agents/agents.md 將會員中心樣式收斂為主站唯一來源:pages/member-center.html 只載入 css/main.css,共用會員中心 partial 補上 .memberCenterPage 頁面根 class,booking 入口改為載入主站會員中心樣式與 booking 自身 header/footer。booking/css/member-center-main.scss、booking/css/member-center-main.css 與 booking/css/pages/_member-center.scss;後續會員中心樣式統一從 css/pages/_member-center.scss 維護。docs/itcss-architecture.md、plans/booking-itcss-scss-plan.md 與 package.json,移除 booking 會員中心獨立 Sass 入口與格式化範圍中的已刪除 CSS 輸出。.agents/agents.md 產出 plans/booking-itcss-scss-plan.md,整理 booking CSS 漸進轉為 SCSS ITCSS 的分層目標、檔案遷移順序、命名轉換策略、編譯方式與驗收清單。booking/css/booking-main.scss 與 booking/css/member-center-main.scss,並建立 settings、generic、elements、objects、components、pages、overrides、utilities 的 SCSS partial;舊平面 CSS source 已移除,Sass 編譯後仍輸出既有 booking-main.css 與 member-center-main.css,保持 HTML 載入路徑相容。:focus-visible 保底;外部 Bootstrap Icons / Flatpickr 類別保留原名。package.json 的 stylelint 範圍改為檢查主站與 booking 的 SCSS source,format 收斂為 booking code 與本輪更新文件範圍,另保留 format:all 作為全專案格式檢查;驗證已通過 bundled Node 執行的 Sass 編譯、SCSS stylelint、booking 範圍 Prettier、Vite build,以及 8 個 booking 頁面在 375/768/1024/1440 viewport 無水平捲動。.agents/AGENTS.md 調整共用登入 Modal:#loginModal 改為右側滑出視窗並放大至視窗高度,社群登入按鈕補齊垂直間距,Facebook 使用既有 info token 混合淺藍底,LINE 使用 --yui-success 背景。siteMenuButton 與 siteCartButton 既有 HTML 已是 type="button",保留原本彈出視窗方式。.agents/AGENTS.md 整理 js/components/member-center.js:使用 Prettier 展開壓縮式單行程式,補齊函式間空行、縮排與中文用途註解,並將折價券與通知 HTML 字串拆成逐標籤換行。node --check js/components/member-center.js,並確認沒有 140 字以上長行。.agents/agents.md 補齊本輪 CSS 細節:商品詳情頁籤列移除右側橫向捲動,會員中心 header 品牌字體避開 booking @font-face 覆蓋,並讓浮動回頂部按鈕維持橘色 48px 共用樣式。.agents/agents.md 調整前台 CSS 細節:商品詳情主內容補左右留白、header 品牌 logo 與 Yuruicamp 文字放大、全站連結 hover 改為不顯示底線、會員中心訂單明細 Modal 美化,並統一浮動回頂部按鈕 icon 尺寸。_header.scss 補齊共用 modal 基礎樣式,js/main.js 與 booking/js/layout.js 在 shared-auth 注入後載入 modal.js,恢復登入、社群登入、個人化問卷與會員下拉選單初始化順序。isLoggedIn 為 true 時隱藏 .siteLoginButton,並顯示 .siteUserMenu;未登入時恢復登入按鈕既有 inline-flex 顯示,避免與會員選單同時出現。.agents/agents.md 指定的前台細節:共用 header 改由掛載點維持 sticky、品牌 Logo 置中、會員下拉與購物車 badge 改成可見狀態才套用 display、購物車移除改垃圾桶 SVG、商品數量與 checkout CTA 套用 token 按鈕樣式,並補齊 checkout-success header icon 樣式來源。.agents/agents.md 補齊新版 header.partial 互動:js/main.js 在 partial 注入後一律執行可重複的 header / modal / cart 初始化,js/components/header.js 改用現有 keyword 搜尋導頁、維持搜尋下拉隱藏並同步登入狀態與會員選單。pages/products.html、js/pages/product-list.js 與商品頁 SCSS 新增 keyword 搜尋結果、0.1 顆星裁切評分、廣告輪播複製首張後無縫回跳、手機篩選按鈕共用商品 CTA 視覺與價格欄位 step="100"。pages/home.html 相關首頁邏輯與 SCSS 補上 0.1 顆星裁切、品牌跑馬燈維持兩組品牌無限捲動、商品卡 hover 改為整卡平滑微放大 / 上移 / 加深陰影,並調整服務特色標題顯示。cmd /c "cd /d D:\GithubDesk\Yuruicamp\css && npx sass main.scss:main.css"、受控啟動 cmd /c "npx sass --watch main.scss:main.css"、node --check 已針對 header.js、home.js、product-list.js 通過;--watch 程序已停止。components/header.partial 與 components/footer.partial,保留 data-layout-part、指定 id、共用登入 modal、購物車 drawer、booking header/footer 入口,並將 header/footer 相關 class 統一為 camelCase。js/components/header.js、js/components/cart.js、js/components/modal.js、booking/js/booking-header.js,把 offcanvas、搜尋層、使用者選單、cart drawer、booking panel 與 shared modal 狀態統一為 .isOpen / .isVisible / .isSelected。css/components/content/pages/_header.scss 與 _footer.scss,改用 --yui-* token,移除 header/footer 範圍內的 .btn、.container、BEM、migrated 與 .active 依賴;同步補上 booking 頁面載入的 booking/css/booking.css 相容樣式。node --check、ESLint、Stylelint、npm.cmd run build 通過;build 仍保留既有非 module script 與 Sass deprecation 警告。探索戶外,從這裡開始 🏕️
假資料已整合至 /data/**(多在 frontend/data/**);PostgreSQL 以 docs/latest_schema.sql 為準(給 Java bootcamp 銜接用,前端仍可走 Mock):
| 文件 | 說明 |
|---|---|
| docs/latest_schema.sql | PostgreSQL 現行 DDL(ENUM + 主表 PK/FK + View/Trigger) |
| docs/database-schema-guide.md | ER 圖與資料字典導覽 |
| docs/schema-enums.md | 狀態/分類枚舉允許值 |
| docs/database-documents/ | 各業務表領域說明(含快照欄位語意) |
| docs/seed/README.md | PostgreSQL 開發 Seed:SQL 結構、載入順序、執行方式與維護規則 |
| plans/data-integration-spec.md | 前端 Mock JSON:資料語意、關聯、衍生資料與維護規則 |
| plans/schema-migration-checklist.md | Schema 整合任務勾選清單(歷史) |
Yuruicamp 是一個完整的露營選物電商網站前端實現,包含 storefront/pages/ 下 11 個買家功能頁面、Mock API 層、完整 RWD 響應式設計,以及一套獨立的賣家管理後台(含員工 ID 登入、九大管理模組、逐頁 view/edit 權限、圖表儀表板)。
開發目標:能跑 → 看懂 → 好改 → 效能,按此優先順序逐步實現。
技術棧:
| 技術 | 用途 |
|---|---|
| HTML5 | 語義化頁面結構 |
| SCSS / CSS3 | 買家前台樣式系統、約 4900 行完整 CSS |
| Vanilla JavaScript | 買家前台頁面互動邏輯(無框架依賴) |
| Vite + Sass | SCSS 編譯、多頁面建置、資產壓縮 |
| ESLint + Prettier + Stylelint | JS / HTML / CSS / SCSS 基礎品質檢查 |
| Bootstrap 5 + jQuery 3 + Chart.js | 賣家後台 UI 框架、圖表視覺化 |
| Mock API(localStorage / sessionStorage + JSON) | 模擬前後台資料,預留真實 API 接入點 |
| Git | 版本控制 |
建置狀態:✅ 買家前台 14 階段完成 + 賣家後台 9 模組完成(2026/06/15,含租借多營地庫存與異動員工 ID)+ 預約子系統 6 頁面完成(2026/06/12)
詳細改檔對照見
userguide.md。前端路徑皆在frontend/底下。
Yuruicamp/
├── frontend/ # ⭐ npm / Vite 根(三前端 + mock)
│ ├── package.json # Vite、lint、format、stylelint、smoke
│ ├── vite.config.js
│ ├── index.html # 品牌入口(重定向至 storefront/pages/home)
│ ├── storefront/ # 主站(裝備商城:pages/ + js/ + css/)
│ ├── components/ # 共用 HTML partial(暫放 frontend 根)
│ ├── booking/ # 營地預約子站(pages/ + js/ + css/)
│ ├── admin/ # 賣家後台(login / dashboard / partials / js)
│ ├── data/ # ⭐ 全站唯一 Mock JSON(執行期 /data/**)
│ ├── assets/ # 圖片、icon、影片
│ ├── src/styles.js # Vite SCSS 入口(匯入 storefront/css)
│ ├── tests/ # smoke 等
│ └── color/ # 色票文件
│
├── backend/ # Spring Boot(本階段架構不動)
├── docs/ # Schema、frontend-specs、database-documents
├── plans/ # 規劃與遷移規格(含 frontend-folder-migration-spec)
├── thoughts/ # 開發思考筆記
├── docker-compose.yml # 本機 PostgreSQL
├── README.md
├── userguide.md # 開發者工作手冊(路徑相對 frontend/)
├── changelog.md
└── .gitignore
方式 1:npm + Vite(強烈推薦,日常請用這個)
開啟終端機,進入前端目錄(不要停在 repo 根):
cd frontend
安裝依賴(第一次或 package.json 有變時):
npm install
啟動開發伺服器:
npm run dev
看終端機印出的位址(預設 http://127.0.0.1:5173),用瀏覽器開啟例如:
為何一定要用 npm/Vite:HTML 已改成根絕對路徑(/storefront/js/...、/data/...、/assets/...),Vite 以 frontend/ 當網站根,這些路徑才會對上。用錯根目錄時會出現「沒有 CSS/JS、後台沒有假資料」。
常用品質檢查(皆在 frontend/ 執行)
cd frontend
npm run smoke # 基礎結構與共用 runtime 檢查
npm run lint # ESLint 檢查 JS
npm run format # Prettier 檢查格式
npm run stylelint # Stylelint 檢查 CSS / SCSS
npm run build # Vite 多頁面建置與資產壓縮
Vite 透過
frontend/src/styles.js匯入storefront/css/main.scss;既有storefront/css/main.css保留作為非 Vite 靜態伺服器 fallback。路徑契約(2026-07): 靜態資源與腳本一律用網站根絕對路徑(
/assets、/storefront/js、/data)。Mock 路徑表在storefront/js/api-mock.js的MockDataPaths;接 Spring 時改AppConfig.USE_MOCK_API = false與API_BASE_URL,不必再改各頁路徑。詳見plans/frontend-root-absolute-path-and-api-contract-spec.md。
方式 2:VS Code Live Server(備援,不建議當日常主路徑)
安裝 Live Server 擴充套件,必須在 frontend/ 目錄右鍵 → Open with Live Server。
不要從 repo 根 Yuruicamp/ 開,否則 /storefront、/data 會 404(沒樣式、沒腳本、後台抓不到資料)。日常開發請優先用上方的 npm run dev。
共用 Header / Footer 片段使用
.partial副檔名,而不是.html。這是為了避免 Live Server 對 HTML fragment 注入 live reload script,造成像components/header這類被fetch()載入的片段 response 截斷。
方式 3:Python 3(備援)
cd frontend
python -m http.server 8000
# 瀏覽器開啟 http://localhost:8000
方式 4:Node.js 靜態伺服器(備援)
cd frontend
npx http-server -p 8000
# 瀏覽器開啟 http://localhost:8000
路徑皆相對 frontend/(dev server root)。
買家前台(購物流程)
入口頁 → index.html
首頁 → storefront/pages/home.html
商品 → storefront/pages/products.html → storefront/pages/product-detail.html
購物 → 任一主站頁右上角購物車 Drawer → storefront/pages/checkout.html → storefront/pages/checkout-success.html
會員 → storefront/pages/member-center.html
內容 → storefront/pages/blog.html → storefront/pages/blog-detail.html
分店 → storefront/pages/branches.html
服務 → storefront/pages/faq.html
賣家後台(管理流程) — 詳見 userguide.md 第 13 節
登入 → admin/login.html(Demo 員工 ID:01 老闆 / 02 員工,密碼任意非空)
後台 → admin/dashboard.html(預設載入第一個有 view 權限的模組)
├── 分析報表 ← Sidebar「分析報表」
├── 訂單管理 ← Sidebar「訂單管理」
├── 庫存異動紀錄 ← Sidebar「庫存異動紀錄」
├── 商品與庫存 ← Sidebar「商品與庫存」
├── 客戶管理 ← Sidebar「客戶管理」
├── 折扣管理 ← Sidebar「折扣管理」
├── 評論管理 ← Sidebar「評論管理」
├── 預約/租借管理 ← Sidebar「預約/租借管理」
└── 權限管理 ← Sidebar「權限管理」
登出 → Sidebar 底部或 Topbar 頭像 → 登出(清除 5 個 sessionStorage key,返回登入頁)
💡 後台登入狀態用
sessionStorage(5 個 key);員工主檔用localStorage.adminEmployees。關閉分頁後 session 自動清除,不影響買家前台的localStorage。
預約系統(預約流程)
搜尋 → booking/pages/camp-search.html(篩選地區、環境、設施)
詳情 → booking/pages/camp-detail.html(選日期、選營位類型,寫入 localStorage.bookingCart)
租借 → booking/pages/camp-rental.html(加選裝備,更新 bookingCart)
結帳 → booking/pages/booking-cart.html(確認明細、填聯絡資訊、送出預約)
說明 → booking/pages/rental-guide.html(租借流程圖文說明)
FAQ → booking/pages/booking-faq.html(預約與退款常見問題)
💡 預約系統使用獨立的
localStorage.bookingCart儲存跨頁資料,與電商購物車的localStorage.cart完全分離,互不干擾。
| 用途 | 色碼 | 預覽 |
|---|---|---|
| 主色 Primary | #244d4d |
深青綠(品牌主軸) |
| 副色 Secondary | #779999 |
淺青灰綠 |
| 成功 Success | #4caf50 |
綠色 |
| 危險 Danger | #d32f2f |
紅色 |
| 輕背景 | #f6fbf6 |
淺綠底 |
| 深 Hover | #316868 |
按鈕懸停 |
所有色彩定義於 css/variables.scss,並由 main.css 的 CSS Custom Properties 引入。
💡 預約子系統色彩差異:預約端 Header 背景使用 booking token,並由
booking/css/settings/_tokens.scss管理--yc-*source of truth 與--bk-*相容 alias;公開頁載入編譯後的booking/css/booking-main.css。
| 斷點 | 寬度 | 目標裝置 |
|---|---|---|
| xs | < 576px | iPhone SE、小型 Android |
| sm | 576–767px | 大型手機 |
| md | 768–991px | iPad 直式 |
| lg | 992–1199px | iPad 橫式、筆電 |
| xl | 1200–1399px | 桌上型電腦 |
| xxl | ≥ 1400px | 大型螢幕 |
手機版特別處理:
input / select 強制 font-size: 16px 避免 iOS Safari 頁面縮放.navbar-offcanvas 補齊 position:fixed; transform:translateX(-100%),預設隱藏,點漢堡☰後滑入)js/state.js)// 讀取
window.AppState.isLoggedIn; // Boolean - 是否已登入
window.AppState.currentUser; // Object - 當前用戶資料
window.AppState.cart; // Array - 購物車商品列表
window.AppState.preferences; // Object - 個人化喜好
// 持久化(寫入 localStorage)
window.saveAppState();
// 重置(只清除 Yuruicamp 已知狀態 key,不清空同網域其他資料)
window.resetAppState();
window.API)// 商品
await window.API.products.getAll(filters); // 取得商品列表(支援篩選)
await window.API.products.getById(productId); // 取得單一商品詳情
// 用戶
await window.API.users.login(email, password); // 模擬登入
await window.API.users.getProfile(userId); // 取得用戶資料
// 訂單
await window.API.orders.getAll(userId); // 取得用戶訂單列表
await window.API.orders.create(orderData); // 建立訂單(模擬)
// 文章
await window.API.articles.getAll(); // 取得文章列表
await window.API.articles.getById(articleId); // 取得文章詳情
// 分店
await window.API.branches.getAll(); // 取得分店列表
💡 日後接入真實後端只需修改
js/api-mock.js的實作,頁面邏輯無需改動。
// Toast 提示
window.showToast(message, type);
// type: 'success' | 'error' | 'warning' | 'info'
// 範例:window.showToast('已加入購物車', 'success')
// Modal 對話框
window.openModal(modalId); // 開啟 Modal
window.closeModal(modalId); // 關閉 Modal
// 範例:window.openModal('loginModal')
// 購物車操作
window.addToCart(product, quantity); // 加入購物車
window.removeFromCart(productId); // 移除商品
window.updateCartQuantity(productId, qty); // 更新數量
window.clearCart(); // 清空購物車
window.openCartDrawer(); // 開啟右側購物車視窗
window.closeCartDrawer(); // 關閉右側購物車視窗
window.renderCartDrawer(); // 依 AppState.cart 重繪 Drawer
formatters.js / validators.js / cart-service.js)window.formatCurrency(3500); // → 'NT$3,500'
window.formatDate("2026-06-03"); // → '2026/06/03'
window.generateId(); // → 'id-1748922345-abc123xyz'
window.isValidEmail("a@b.com"); // → true / false
window.isValidPhone("0912345678"); // → true / false
window.calculateCartTotal(); // → Number(購物車總金額)
window.calculateShippingFee(total); // → 0 或 60(依免運門檻)
window.debounce(fn, 300); // 防抖(搜尋框使用)
window.throttle(fn, 100); // 節流(滾動事件使用)
| 鍵 | 型別 | 說明 |
|---|---|---|
isLoggedIn |
Boolean | 登入狀態 |
currentUser |
Object / null | 當前用戶資料 |
cart |
Array | 電商購物車商品([{id, name, price, quantity, ...}]) |
preferences |
Object | 個人化問卷結果(風格偏好、裝備需求) |
theme |
String | 主題(預留,目前固定 'light') |
memberProfile |
Object | 會員中心儲存的個人資料 |
bookingCart |
Object | 預約購物車({booking_info, selected_zones, selected_rentals, summary}) |
mockCheckoutSessions |
Array | 契約化 Checkout Mock Session 與內部冪等資料 |
adminEmployees |
Array | 後台員工清單與逐頁權限(permissions.js 種子初始化) |
⚠️
cart(電商)與bookingCart(預約)是兩個完全獨立的 localStorage key,互不干擾。
resetAppState()只移除 Yuruicamp 已知狀態,包含mockOrders與mockCheckoutSessions;不使用localStorage.clear(),避免誤刪同網域其他專案資料。
商城 Checkout:
| 鍵 | 型別 | 說明 |
|---|---|---|
checkoutIdempotencyKey |
String | 建立 Checkout 使用的 UUID |
checkoutCartFingerprint |
String | 購物車規格與數量指紋 |
checkoutCompletedOrderId |
String | 建立成功的後端訂單 ID |
後台:
| 鍵 | 型別 | 說明 |
|---|---|---|
adminLoggedIn |
String | "true" 表示已登入 |
adminId |
String | 員工 ID(例:"01") |
adminName |
String | 顯示名稱 |
isSuperAdmin |
String | "true" / "false" |
adminPermissions |
String | JSON 字串,各 section 的 { view, edit } |
所有頁面已套用以下最佳化措施:
<link rel="preconnect">:所有 HTML 預先與外部圖片伺服器建立連線,減少 DNS 查詢延遲<script defer>:所有 JS 延遲載入,不阻塞 HTML 解析與首屏渲染loading="lazy":非首屏圖片懶加載,減少初始請求數IntersectionObserver 模擬<meta name="theme-color">:手機瀏覽器狀態列顯示品牌綠色 #244d4dwill-change:動畫元素預先通知瀏覽器 GPU 合成,動畫更流暢contain: layout style,限制重排範圍@media (prefers-reduced-motion):尊重使用者「減少動態效果」的無障礙偏好@media print:列印樣式隱藏導航、影片、Toast 等非必要元素| 瀏覽器 | 最低版本 | 說明 |
|---|---|---|
| Chrome / Edge | 90+ | 完整支援 |
| Firefox | 88+ | 完整支援 |
| Safari | 14+ | 已加入 -webkit- 前綴、iOS 縮放修正、Safe Area 支援 |
| Samsung Internet | 14+ | 基於 Chromium,完整支援 |
已處理的相容性問題:
-webkit- vendor prefixselect 下拉箭頭 Safari 樣式修正appearance: none 跨瀏覽器表單樣式統一scroll-behavior: smooth 降級處理Mock API 採用適配器模式。切換真後端時,頁面仍呼叫 window.API/BookingAPI/AdminAPI,Token 與 REST 細節統一交給 api-client.js。
目前(Mock):
// js/api-mock.js 內部從 JSON 檔讀取
window.API.products.getAll = async (filters) => {
const data = await fetch("../data/products.json").then((r) => r.json());
return data.filter(/* ... */);
};
真實 API facade:
// facade 呼叫共用 REST 層,pages/*.js 不自行 fetch
window.API.products.getAll = async (filters) => {
return window.ApiClient._restRequest("/products", {
auth: "optional",
});
};
Firebase 初始化後注入 Auth:
window.AppAuth.configure({ auth: firebaseAuth });
本機 dev: Token 只能透過開發認證設定或 AppAuth.configure() 提供,不可寫死在 Checkout 頁面。API Base URL 設定在 storefront/js/config.js:
window.AppConfig.API_BASE_URL = "http://localhost:8080/api";
詳細規則與驗證步驟見 docs/frontend-specs/api/auth-rest-client.md。
| 項目 | 買家前台 | 賣家後台 | 預約系統 | 合計 |
|---|---|---|---|---|
| HTML 頁面 | 11 個 | 2 個(login + dashboard)+ 9 個 partials | 6 個 | 28 個 |
| JavaScript 模組 | 19 個(6 元件 + 10 頁面 + 3 核心) | 10 個(permissions + core + 8 功能) | 5 個 | 34 個 |
| CSS 檔案 | 1 個(main.css) | 1 個(admin.css) | 1 個(booking-main.css) | 3 個 |
| Mock 資料 JSON | 全站共用 /data/**(13 檔,見 data-paths.js) |
— | — | 13 個 |
| RWD 斷點 | 6 個(xs / sm / md / lg / xl / xxl) | Bootstrap 5 斷點(同套) | 768px 主要斷點 | — |
| 儲存機制 | localStorage(8 個鍵,含 bookingCart、adminEmployees) | sessionStorage(5 個 key) | localStorage.bookingCart | — |
以下指令請先
cd frontend再執行(package.json已不在 repo 根)。
| 指令 | 目的 |
|---|---|
npm run dev |
啟動 Vite 開發伺服器,支援多頁面與 SCSS entry |
npm run build |
使用 Vite 建置 HTML、JS、SCSS 與資產壓縮輸出 |
npm run lint |
以 ESLint 檢查主站與 booking JS 語法與基礎維護風險 |
npm run format |
以 Prettier 檢查 HTML / CSS / SCSS / JS / JSON / Markdown 格式 |
npm run stylelint |
以 Stylelint 檢查 SCSS 與 booking CSS |
npm run smoke |
檢查共用 header/cart drawer、拆分 runtime 載入順序、Vite/tooling 檔案是否齊全 |
| 方向 | 說明 |
|---|---|
| 接入真實後端(前台) | 修改 js/api-mock.js 的各函數實作,頁面邏輯零改動 |
| 接入真實後端(後台) | 修改 admin/js/*.js 中各 fetch('../data/xxx.json') 及 permissions.js 的 localStorage 邏輯改為真實 API |
| 後台密碼驗證 / 操作日誌 | 逐頁 view/edit 權限已完成;待辦:密碼後端驗證、審計紀錄(見 plans/adminPermissions.md) |
| 升級至 SPA | 以 Vue 3 或 React 重構,可直接複用現有 CSS 設計系統與 JSON 資料 |
| 加入數據分析 | 在 main.js 的 initGlobalListeners() 接入 GA4 / GTM 事件追蹤 |
| 自動化測試 | 以 Playwright 或 Cypress 撰寫自動化測試腳本 |
| 深色模式 | main.css 已預留 @media (prefers-color-scheme: dark) 區塊 |
| PWA | 加入 manifest.json 與 Service Worker 支援離線瀏覽 |
版本:1.3.76
最後更新:2026/07/06
完整更新紀錄請見 changelog.md