此專案為成大社聯會數位平台,主要提供:
- 公開頁面:公告、表單填寫、點名頁面
- 管理後台:表單管理、點名管理、保證金管理、內容管理
- 資料來源整合:由學校社團平台抓取社團資料,統一匯入 YAML/Firestore
核心技術:
- Frontend / Backend:Next.js (App Router)
- Database:Firebase Firestore
- Auth:Firebase Session
- 腳本:TypeScript (tsx) + Python
| 介面 | 圖片 |
|---|---|
| 1. 首頁 | ![]() |
| 2. 最新消息 | ![]() |
| 3. 文章區塊 | ![]() |
| 4. 後臺 Dashboard | ![]() |
主要路徑:
web/:網站主程式(含後台、API)scripts/:資料同步與種子腳本data/:靜態資料與 YAML 資料檔
權限角色:
- 一般使用者:可登入、填表、簽到
- 管理員:admin(需在
users/{uid}.role = "admin")
- Node.js 20+
- npm
- Python 3.10+
- (若要跑抓資料腳本)Playwright Chromium
在專案根目錄執行:
cd web
npm installcd web
npm run dev預設網址:http://localhost:3000
本專案同時使用 Firebase Client SDK(前端登入)與 Admin SDK(後端 API / seed)。
建議先複製環境檔:
cd web
copy .env.example .env- 進入 Firebase Console,建立新專案(或使用既有專案)。
- 啟用 Firestore Database(建議先用測試模式,之後再收斂規則)。
- 到 Authentication -> Sign-in method。
- 啟用
Google登入。 - 在 Authorized domains 加入開發與正式網域(例如
localhost)。
說明:
- 本專案登入流程使用 Google Provider。
- 前端程式會限制僅允許
@gs.ncku.edu.tw信箱登入。
- 在 Firebase 專案設定新增 Web App。
- 取得 SDK 設定值,填入
web/.env:
NEXT_PUBLIC_FIREBASE_API_KEYNEXT_PUBLIC_FIREBASE_AUTH_DOMAINNEXT_PUBLIC_FIREBASE_PROJECT_IDNEXT_PUBLIC_FIREBASE_STORAGE_BUCKETNEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_IDNEXT_PUBLIC_FIREBASE_APP_ID
- 到 Firebase Console -> Project settings -> Service accounts。
- 點擊
Generate new private key,下載 JSON 金鑰。 - 將 JSON 內容 base64 編碼後,填入:
FIREBASE_ADMIN_SERVICE_ACCOUNT_BASE64
PowerShell 範例(Windows):
$bytes = [System.IO.File]::ReadAllBytes("service-account.json")
[Convert]::ToBase64String($bytes)若有使用 Firebase Storage Admin 操作,可額外設定:
FIREBASE_STORAGE_BUCKET(未設定時,系統會 fallback 到NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET)
- 啟動網站:
cd web && npm run dev。 - 到登入頁測試 Google 登入。
- 執行 seed:
cd web && npm run seed。 - 確認 Firestore 有成功寫入
clubs與site_content。
若要啟用後台圖片上傳(公告內容內文圖片、封面圖),請在 web/.env 設定:
CLOUDFLARE_R2_ACCOUNT_IDCLOUDFLARE_R2_ACCESS_KEY_IDCLOUDFLARE_R2_SECRET_ACCESS_KEYCLOUDFLARE_R2_BUCKETCLOUDFLARE_R2_PUBLIC_BASE_URL(例如https://<public-host>)
說明:
- 系統會直接回傳公開 URL,不使用簽名 URL。
- 請先確保 R2 Bucket 已設定公開讀取(Public Access)。
本節會一步一步說明如何取得每一個 R2 參數。
前置條件:
- 你有 Cloudflare 帳號,且已啟用 R2。
- 你有該 Cloudflare 帳號的 R2 操作權限。
- 登入 Cloudflare Dashboard。
- 左側選單選擇
R2 Object Storage -> Overview。
- 在 Overview 頁面點擊
Create bucket。 - 輸入 bucket 名稱(例如:
ncku-ca-images)。 - 在右側
Account Details區塊取得Account ID。
你會得到:
CLOUDFLARE_R2_BUCKET:就是你建立的 bucket 名稱。CLOUDFLARE_R2_ACCOUNT_ID:Cloudflare 帳號的 Account ID。
- 在 R2
Overview頁面的右側Account Details區塊。 - 點擊
Manage R2 API Tokens。
- 點擊
Create API token。 - 依需求填寫欄位:
Token Name:例如ncku-ca-r2-token。Permissions:一般上傳場景建議選Object Read & Write。Specify Bucket(s):建議只授權目標 bucket(最小權限原則)。TTL:可選Forever,或設定到期時間。
- 點擊
Create API Token。
建立後會顯示:
Access Key IDSecret Access Key(通常只顯示一次,請立即保存)
說明:
- 本專案使用的是 R2 API Token 建立出的 S3 相容金鑰,不是 Cloudflare Global API Key。
建議權限:
- 最小權限原則,至少允許目標 bucket 的
Object Read與Object Write。
請立刻把 Access Key / Secret Key 保存到密碼管理工具或安全位置。
填入:
CLOUDFLARE_R2_ACCESS_KEY_ID:對應 Access Key ID。CLOUDFLARE_R2_SECRET_ACCESS_KEY:對應 Secret Access Key。
目前系統是「上傳後直接回傳公開 URL」,所以需要可公開讀取的 base URL。
常見做法:
- 直接使用 R2 的公開網域(若你已啟用 Public bucket)。
- 使用自訂網域(Custom Domain)指向 R2 bucket(建議正式環境)。
設定要求:
CLOUDFLARE_R2_PUBLIC_BASE_URL必須是「不含尾端斜線」的基底網址。- 範例:
https://pub-xxxxxxxx.r2.devhttps://cdn.your-domain.com
將以下內容填入 web/.env:
CLOUDFLARE_R2_ACCOUNT_ID=你的AccountID
CLOUDFLARE_R2_ACCESS_KEY_ID=你的AccessKeyID
CLOUDFLARE_R2_SECRET_ACCESS_KEY=你的SecretAccessKey
CLOUDFLARE_R2_BUCKET=你的Bucket名稱
CLOUDFLARE_R2_PUBLIC_BASE_URL=https://你的公開網域另外在 web/.env.example 也保留同樣欄位(不放真值),避免團隊成員遺漏設定。
- 啟動網站:
cd web && npm run dev。 - 以管理員登入後台。
- 到文章管理頁或 Markdown 編輯器上傳圖片。
- 成功後檢查:
- 回傳連結是否為你設定的
CLOUDFLARE_R2_PUBLIC_BASE_URL網域。 - 瀏覽器可直接開啟該圖片 URL。
- 回傳連結是否為你設定的
若上傳失敗,優先檢查:
- Access Key / Secret 是否貼錯。
- Token 權限是否包含目標 bucket 的寫入權限。
- Bucket 名稱是否一致。
- Public Base URL 是否可公開存取。
後台主要入口(登入且具 admin 權限):
/admin/forms:表單管理/admin/attendance:點名管理/admin/deposit:保證金管理
建議作業順序(典型活動):
- 建立表單(必要時含保證金策略)
- 活動當日建立點名活動並發送點名密碼
- 活動後依表單回覆與活動規則處理保證金狀態
- 登入管理員帳號。
- 進入
/admin/forms。 - 可看到現有表單清單與狀態:
草稿、開放中、已關閉。
點擊「新增」後,會先出現模板選擇器:
- 套用模板:快速帶入常用欄位與設定(如社博、寒假場協、一般報名)
- 空白表單:完全自行設計欄位
建立或編輯時,至少確認:
title:表單名稱(必填)description:對外說明form_type:表單類型status:draft:草稿,不對外使用open:開放填寫closed:停止填寫
closes_at:截止時間(建議設定)
可在表單中設定:
required:是否需保證金amount:保證金金額binding_mode:linked_to_response:綁定表單回覆independent:獨立流程管理
建議:
- 活動報名類型(社博、場協)設
required = true - 問卷類型設
required = false
可配置欄位型別包含:
- text / textarea / number
- email / phone / date
- select / radio / checkbox
- file
- club_picker
- section_header(段落標題)
重點設定:
required:是否必填options:選項型欄位內容validation:最小值、最大值、regex 等depends_on:條件顯示(進階)
系統提交時會做以下檢查:
- 使用者必須登入
- 表單存在且未截止
- 必填欄位檢查
- 欄位格式檢查(email、phone、number、選項合法性)
- 同一社團同一表單不可重複送出(重複會回傳錯誤)
建議上線前逐項確認:
- 狀態已切到
open - 截止時間正確
- 必填欄位與提示文字完整
- 若需保證金,金額與規則已寫清楚
- 用測試帳號實際送出一次
進入 /admin/forms/{form_id} 可:
- 查看表單預覽
- 檢視回覆列表
- 開啟單筆回覆詳情
- 匯出 CSV
點名分為兩端:
- 管理端:建立點名活動、查看即時簽到、補點、匯出
- 使用端:社團登入後到
/attendance送出簽到
- 進入
/admin/attendance - 點擊新增活動
- 填寫:
title:活動名稱description:活動說明opens_at:開始時間closes_at:結束時間(未填時系統預設 +2 小時)expected_categories:預期點名社團類別(A-H)passcode:點名密碼(必填)
- 儲存後,活動會出現在列表,狀態會依時間顯示為即將開始/進行中/已結束。
社團操作 /attendance:
- 使用者先登入
- 選擇社團
- 輸入管理端提供之點名密碼
- 送出簽到
系統檢查:
- 活動在開放時間內
- 密碼正確
- 社團在本次點名名單內
- 同一社團不可重複簽到
在 /admin/attendance 可:
- 看到每場活動已簽到數 / 應簽到數
- 開啟詳情檢視各社團簽到狀態
- 匯出 CSV(含簽到時間與狀態)
若社團現場有技術問題,管理端可在活動詳情對未簽到社團執行「手動補點名」。
建議補點流程:
- 先確認社團身分與到場事實
- 由管理員補點
- 於備註或內部紀錄標示補點原因(例如網路問題)
保證金管理頁為 /admin/deposit,每筆紀錄主要狀態:
pending_payment:待繳paid:已繳returned:已退還
管理員可進行:
- 搜尋社團(名稱 / ID)
- 依狀態篩選
- 單筆操作:
- 待繳 -> 標記已繳
- 已繳 -> 退還保證金
- 批次操作:
- 多筆待繳 -> 批次標記已繳
- 多筆已繳 -> 批次標記已退還
- 編輯備註(例如匯款末五碼、退費日期、人工核對註記)
- 匯出 CSV
建議每場活動依下列節奏執行:
- 活動報名期間:追蹤
pending_payment - 截止日前:逐筆核帳並轉
paid - 活動結束後:依規則審核是否退還,更新為
returned - 每次狀態更新都補上備註,保留可稽核軌跡
- 每週匯出一次 CSV 與金流對帳
- 重要活動(社博)建議雙人覆核
- 若發生例外退費,需在備註記錄原因與時間
本章是你提出的重點流程:
- 從學校平台抓取最新社團資料
- 產生
data/ncku-clubs.yaml - 匯入 Firestore
clubs集合
使用腳本:scripts/scrape_ncku_clubs.py
功能:
- 從學校系統
club0408抓取 A-H 分類社團 - 可手動登入後帶 cookie 抓取
- 逐筆呼叫明細 API(檢視)
- 輸出標準化 YAML
在專案根目錄執行:
pip install requests beautifulsoup4 pyyaml playwright
playwright install chromiumpython scripts/scrape_ncku_clubs.py執行後流程:
- 腳本會開啟瀏覽器
- 你在瀏覽器手動登入學校系統
- 回到終端機按 Enter
- 腳本開始抓取各分類資料
- 輸出 YAML 到預設路徑:
data/ncku-clubs.yaml
# 指定輸出路徑
python scripts/scrape_ncku_clubs.py --output data/ncku-clubs.yaml
# 若 session 已可直接存取,可跳過手動登入
python scripts/scrape_ncku_clubs.py --skip-login
# 額外包含 I 類(所學會)
python scripts/scrape_ncku_clubs.py --include-institute
# 調整逾時秒數
python scripts/scrape_ncku_clubs.py --timeout 60至少檢查:
meta.total_clubs是否合理meta.scraped_at是否為本次時間clubs[].id、category_code、status是否存在
本專案已有種子腳本:scripts/seed-firestore.ts
執行方式(推薦):
cd web
npm run seed或:
cd web
npx tsx ../scripts/seed-firestore.ts此腳本會:
- 讀取
data/formatted/*.md寫入site_content - 讀取
data/ncku-clubs.yaml寫入clubs - 依社團狀態自動設定
is_active(status == 正式)
建議每學期或大型活動前執行一次:
- 執行抓取腳本更新 YAML
- 檢查 YAML 資料品質
- 執行 seed 匯入 Firestore
- 後台 spot check 幾個社團資料是否正確
以下為活動前到活動後的完整循環:
- 更新社團主檔
- 跑
scrape_ncku_clubs.py - 跑
npm run seed
- 跑
- 建立活動報名表
- 選模板
- 設定截止與保證金
- 狀態改為
open
- 活動當天點名
- 建立點名活動與密碼
- 監看簽到情況
- 例外情形補點
- 活動後保證金作業
- 對帳後更新狀態
- 註記退費資訊
- 匯出 CSV 存檔
檢查:
- 是否已登入
users/{uid}.role是否為admin- Session 是否過期(重新登入)
檢查:
- 表單狀態是否為
open - 是否已過
closes_at - 必填欄位與格式是否符合
- 是否同社團重複送出
檢查:
- passcode 是否含多餘空白
- 活動時間是否已開始且未截止
- 社團是否屬於本次點名範圍
檢查:
web/.env是否有FIREBASE_ADMIN_SERVICE_ACCOUNT_BASE64- base64 JSON 是否有效
data/ncku-clubs.yaml檔案是否存在且可解析
# 啟動開發
cd web
npm run dev
# 型式檢查
npm run lint
# 建置檢查
npm run build
# 匯入資料(clubs + site_content)
npm run seed
# 抓取學校社團資料(在專案根目錄)
python scripts/scrape_ncku_clubs.py若後台流程有調整,請同步更新本文件以下章節:
- 第 5 章(表單)
- 第 6 章(點名)
- 第 7 章(保證金)
- 第 8 章(資料抓取與匯入)
維持「系統實作」與「操作文件」一致,能大幅降低交接成本。



